bitfab 0.38.3 → 0.40.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 +59 -26
- data/lib/bitfab/datasets.rb +107 -0
- data/lib/bitfab/version.rb +1 -1
- data/lib/bitfab.rb +5 -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: a178d05e7c40055c3ac8dec21a40eb60eb36b08a005dfa55d031c3c3b9cdf129
|
|
4
|
+
data.tar.gz: e71a921a9bad32b967c723245c1187257366073f13aab6f8dca2882d6b758fb8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 559fed18dd194a527ff477e057f465a172d7266ba05c4e6b3305a693091b0fa1de3847078d8f0df4c6822a4b28b2fd6a0f802eaf2db7d75e38233051f05fc887
|
|
7
|
+
data.tar.gz: f4a718c47568294809eaed27905b2d8b67a3058fc954eff2dac6b10a05ae2219463d1742b40bb0a7725d4979a5c86c2beb37e9e10621a588e007a9a7757d6376
|
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
|
|
@@ -690,7 +717,7 @@ module Bitfab
|
|
|
690
717
|
# still rides out with dropped: true, so the server scrubs any sibling
|
|
691
718
|
# spans that already raced out before the flag was set.
|
|
692
719
|
trace_dropped = TraceState.get(trace_id)&.dig(:dropped) == true
|
|
693
|
-
return
|
|
720
|
+
return if trace_dropped
|
|
694
721
|
|
|
695
722
|
# Human-readable JSON (input/output fields). The *_with_report variants
|
|
696
723
|
# also report what could not be faithfully captured so a lossy span is
|
|
@@ -772,8 +799,8 @@ module Bitfab
|
|
|
772
799
|
# mocked) so unmarked spans don't silently shift subsequent marked
|
|
773
800
|
# spans' indices. Different (key, name) pairs have independent counters.
|
|
774
801
|
def advance_mock_counter(replay_ctx, trace_function_key, span_name, is_root_span:)
|
|
775
|
-
return
|
|
776
|
-
return
|
|
802
|
+
return if is_root_span
|
|
803
|
+
return unless replay_ctx&.dig(:mock_tree)
|
|
777
804
|
|
|
778
805
|
counters = replay_ctx[:call_counters]
|
|
779
806
|
counter_key = "#{trace_function_key}:#{span_name}"
|
|
@@ -819,6 +846,7 @@ module Bitfab
|
|
|
819
846
|
# output, wrap it in a callable value.
|
|
820
847
|
unless value.respond_to?(:call)
|
|
821
848
|
return [value, "override"] unless value.equal?(NO_MOCK_OVERRIDE)
|
|
849
|
+
|
|
822
850
|
next
|
|
823
851
|
end
|
|
824
852
|
|
|
@@ -826,6 +854,7 @@ module Bitfab
|
|
|
826
854
|
unless mock_entry
|
|
827
855
|
raise "No recorded span to source output for '#{trace_function_key}'."
|
|
828
856
|
end
|
|
857
|
+
|
|
829
858
|
resolve_recorded_output(mock_entry, replay_ctx)
|
|
830
859
|
end
|
|
831
860
|
resolved = value.call({node:, inputs: args, kwargs:, get_original_output:})
|
|
@@ -847,7 +876,7 @@ module Bitfab
|
|
|
847
876
|
unless mock_entry
|
|
848
877
|
raise StandardError,
|
|
849
878
|
"Replay selected span '#{trace_function_key}:#{span_name}' for mocking, " \
|
|
850
|
-
|
|
879
|
+
"but recorded occurrence #{call_index + 1} is unavailable. The real span was not executed."
|
|
851
880
|
end
|
|
852
881
|
|
|
853
882
|
[resolve_recorded_output(mock_entry, replay_ctx), "recorded"]
|
|
@@ -955,9 +984,13 @@ module Bitfab
|
|
|
955
984
|
# mock strategy. A missing selected occurrence fails closed.
|
|
956
985
|
def wrap(klass, method_name, name: nil, type: "custom", capture_when: "always", mock_on_replay: false)
|
|
957
986
|
Bitfab::Traceable.wrap(
|
|
958
|
-
klass,
|
|
987
|
+
klass,
|
|
988
|
+
method_name,
|
|
959
989
|
trace_function_key: @trace_function_key,
|
|
960
|
-
name:,
|
|
990
|
+
name:,
|
|
991
|
+
type:,
|
|
992
|
+
capture_when:,
|
|
993
|
+
mock_on_replay:,
|
|
961
994
|
client: @client
|
|
962
995
|
)
|
|
963
996
|
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/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"
|
|
@@ -119,16 +120,16 @@ module Bitfab
|
|
|
119
120
|
# whose source trace carried no DB snapshot reference
|
|
120
121
|
def current_replay_branch
|
|
121
122
|
ctx = ReplayContext.current
|
|
122
|
-
return
|
|
123
|
+
return unless ctx
|
|
123
124
|
|
|
124
125
|
lease = ctx[:db_branch_lease]
|
|
125
|
-
return
|
|
126
|
+
return unless lease
|
|
126
127
|
|
|
127
128
|
# Surface the Bitfab trace ID (what the customer sees in the dashboard),
|
|
128
129
|
# not the external one. Falling back to the external ID keeps replays from
|
|
129
130
|
# external sources working until that path is fully wired.
|
|
130
131
|
trace_id = ctx[:source_bitfab_trace_id] || ctx[:input_source_trace_id]
|
|
131
|
-
return
|
|
132
|
+
return unless trace_id
|
|
132
133
|
|
|
133
134
|
ReplayBranch.new(lease, trace_id, ctx)
|
|
134
135
|
end
|
|
@@ -159,7 +160,7 @@ module Bitfab
|
|
|
159
160
|
# succeeded, errored, and item (the object replay already passes to
|
|
160
161
|
# on_item_finish)
|
|
161
162
|
def report_replay_progress(progress)
|
|
162
|
-
warn
|
|
163
|
+
warn("#{BITFAB_PROGRESS_PREFIX}#{Replay.json_safe(progress).to_json}")
|
|
163
164
|
rescue
|
|
164
165
|
nil
|
|
165
166
|
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.40.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
|