bitfab 0.51.9 → 0.57.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.
@@ -53,32 +53,28 @@ module Bitfab
53
53
  # preserve the bound client through the fluent wrapper, matching Python's
54
54
  # `BitfabFunction.span()` and TypeScript's `BitfabFunction.withSpan()`.
55
55
  def self.wrap(klass, method_name, trace_function_key:, name: nil, type: "custom",
56
- capture_when: "always", mock_on_replay: false, client: nil)
57
- span_name = name || method_name.to_s
56
+ capture_when: "always", mock_on_replay: false, finalize: nil, test_run_id: nil, client: nil)
58
57
  method_name_str = method_name.to_s
59
58
  bound_client = client
60
-
61
59
  wrapper = Module.new do
62
60
  define_method(method_name) do |*args, **kwargs, &block|
63
- target_client = bound_client || Bitfab.client
61
+ target_client = bound_client || Bitfab.client_or_nil
62
+ next super(*args, **kwargs, &block) unless target_client
63
+
64
+ Subtree.assert_surface!(:span) if target_client.send(:should_record?)
64
65
  target_client.send(:execute_span,
65
66
  trace_function_key:,
66
- span_name:,
67
- span_type: type,
68
- function_name: method_name_str,
69
- args:,
70
- kwargs:,
71
- capture_when:,
72
- mock_on_replay:,
73
- explicit_span_receiver: self,
74
- explicit_span_method_name: method_name) do
67
+ span_name: name || Subtree.qualified_name(self, method_name),
68
+ span_type: type, function_name: method_name_str, args:, kwargs:,
69
+ capture_when:, mock_on_replay:, finalize:, test_run_id:, surface: :span,
70
+ explicit_span_receiver: self, explicit_span_method_name: method_name) do
75
71
  super(*args, **kwargs, &block)
76
72
  end
77
73
  end
78
74
  end
79
-
80
75
  wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
81
76
  wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
77
+ preserve_visibility(klass, wrapper, method_name)
82
78
  klass.prepend(wrapper)
83
79
  end
84
80
 
@@ -87,44 +83,49 @@ module Bitfab
87
83
  # beneath it record automatically as function spans.
88
84
  def self.trace(klass, method_name, trace_function_key:, name: nil, type: "custom",
89
85
  max_depth: Subtree::DEFAULT_MAX_DEPTH, max_spans: Subtree::DEFAULT_MAX_SPANS,
90
- exclude: [], include_wrappers: false, client: nil)
91
- source = source_of(klass, method_name)
92
- capture_config = Subtree.prepare(root_source: source, exclude:)
93
- span_name = name || method_name.to_s
86
+ max_captured_subtree_spans: Subtree::DEFAULT_MAX_CAPTURED_SUBTREE_SPANS,
87
+ exclude: [], include_wrappers: false, mock_on_replay_default: false, client: nil)
88
+ validate_trace_options!(max_spans:)
89
+ capture_config = Subtree.prepare(root_source: source_of(klass, method_name), exclude:)
94
90
  method_name_str = method_name.to_s
95
91
  bound_client = client
96
-
97
92
  wrapper = Module.new do
98
93
  define_method(method_name) do |*args, **kwargs, &block|
99
- if Subtree.active_trace?(trace_function_key)
100
- next super(*args, **kwargs, &block)
94
+ target_client = bound_client || Bitfab.client_or_nil
95
+ next super(*args, **kwargs, &block) unless target_client&.send(:should_record?)
96
+
97
+ Subtree.assert_surface!(:trace)
98
+ next super(*args, **kwargs, &block) if Subtree.nested_root_suppressed?
99
+
100
+ target_client.send(:wait_for_simulation_plan) unless Subtree.current_session
101
+
102
+ span_name = name || Subtree.qualified_name(self, method_name)
103
+ managed = SpanContext.current&.dig(:recording_override)
104
+ if managed && managed.empty? && (ReplayContext.current || SeedContext.current)
105
+ managed.merge!(name: span_name, type:, function_name: method_name_str, instrumentation: "trace")
106
+ SpanContext.current[:surface] = :trace
107
+ next Subtree.capture(
108
+ client: target_client, trace_function_key:, config: capture_config,
109
+ max_depth:, max_spans:, max_captured_subtree_spans:, include_wrappers:, mock_on_replay_default:,
110
+ root_receiver: self, root_method_name: method_name
111
+ ) { super(*args, **kwargs, &block) }
101
112
  end
102
-
103
- target_client = bound_client || Bitfab.client
104
- target_client.send(
105
- :execute_span,
106
- trace_function_key:,
107
- span_name:,
108
- span_type: type,
109
- function_name: method_name_str,
110
- args:,
111
- kwargs:,
112
- independent_root: true
113
- ) do
113
+ call = Subtree::ConfiguredCall.new(receiver: self, method_name:, name: span_name,
114
+ type:, args:, kwargs:, nested_trace_key: trace_function_key)
115
+ target_client.send(:execute_span,
116
+ trace_function_key:, span_name:, span_type: type,
117
+ function_name: method_name_str, args:, kwargs:, independent_root: true,
118
+ surface: :trace, span_data: call.span_data,
119
+ on_start: call.method(:start), on_complete: call.method(:complete),
120
+ around_body: call.method(:run)) do
114
121
  Subtree.capture(
115
- client: target_client,
116
- trace_function_key:,
117
- config: capture_config,
118
- max_depth:,
119
- max_spans:,
120
- include_wrappers:,
121
- root_receiver: self,
122
- root_method_name: method_name
122
+ client: target_client, trace_function_key:, config: capture_config,
123
+ max_depth:, max_spans:, max_captured_subtree_spans:, include_wrappers:, mock_on_replay_default:,
124
+ root_receiver: self, root_method_name: method_name
123
125
  ) { super(*args, **kwargs, &block) }
124
126
  end
125
127
  end
126
128
  end
127
-
128
129
  wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
129
130
  wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
130
131
  preserve_visibility(klass, wrapper, method_name)
@@ -134,55 +135,42 @@ module Bitfab
134
135
  # Configure an existing method only when it is discovered beneath a
135
136
  # bitfab_trace root. Outside subtree capture the method runs unchanged.
136
137
  def self.node(klass, method_name, name: nil, type: "custom", capture: true,
137
- test_run_id: nil, mock_on_replay: false, finalize: nil, client: nil)
138
+ test_run_id: nil, mock_on_replay: nil, finalize: nil, client: nil)
138
139
  validate_node_options!(capture:, mock_on_replay:)
139
- span_name = name || method_name.to_s
140
140
  method_name_str = method_name.to_s
141
141
  bound_client = client
142
-
143
142
  wrapper = Module.new do
144
143
  define_method(method_name) do |*args, **kwargs, &block|
144
+ Subtree.assert_surface!(:trace)
145
145
  session = Subtree.current_session
146
146
  if !session || (bound_client && !session.client.equal?(bound_client))
147
147
  next super(*args, **kwargs, &block)
148
148
  end
149
-
150
149
  unless capture
151
150
  next Subtree.with_suppressed_node(receiver: self, method_name:) do
152
151
  super(*args, **kwargs, &block)
153
152
  end
154
153
  end
155
154
 
156
- if session.excluded?(method_name)
157
- next Subtree.with_suppressed_node(receiver: self, method_name:, sessions: [session]) do
158
- super(*args, **kwargs, &block)
159
- end
155
+ span_name = name || Subtree.qualified_name(self, method_name)
156
+ call = Subtree::ConfiguredCall.new(receiver: self, method_name:, name: span_name,
157
+ type:, args:, kwargs:, test_run_id:)
158
+ unless call.owner
159
+ next call.run { super(*args, **kwargs, &block) }
160
160
  end
161
-
162
- unless session.reserve_node
163
- next Subtree.with_suppressed_node(receiver: self, method_name:, sessions: [session]) do
164
- super(*args, **kwargs, &block)
165
- end
161
+ policy = mock_on_replay.nil? ? call.owner.mock_on_replay_default : mock_on_replay
162
+ call.with_owner_parent do
163
+ call.owner.client.send(:execute_span,
164
+ trace_function_key: call.owner.trace_function_key,
165
+ span_name:, span_type: type, function_name: method_name_str,
166
+ args:, kwargs:, capture_when: "nested", mock_on_replay: policy,
167
+ explicit_span_receiver: self, explicit_span_method_name: method_name,
168
+ test_run_id:, finalize:, surface: :trace, result_recorded_elsewhere: call.recorded_elsewhere?,
169
+ on_start: call.method(:start), on_complete: call.method(:complete),
170
+ around_body: call.method(:run)) { super(*args, **kwargs, &block) }
166
171
  end
167
-
168
- session.client.send(
169
- :execute_span,
170
- trace_function_key: session.trace_function_key,
171
- span_name:,
172
- span_type: type,
173
- function_name: method_name_str,
174
- args:,
175
- kwargs:,
176
- capture_when: "nested",
177
- mock_on_replay:,
178
- explicit_span_receiver: self,
179
- explicit_span_method_name: method_name,
180
- test_run_id:,
181
- finalize:
182
- ) { super(*args, **kwargs, &block) }
183
172
  end
184
173
  end
185
-
186
174
  preserve_visibility(klass, wrapper, method_name)
187
175
  klass.prepend(wrapper)
188
176
  end
@@ -205,6 +193,14 @@ module Bitfab
205
193
  end
206
194
  private_class_method :preserve_visibility
207
195
 
196
+ def self.validate_trace_options!(max_spans:)
197
+ return if max_spans.is_a?(Integer) && max_spans >= 1
198
+
199
+ raise ArgumentError,
200
+ "bitfab_trace max_spans must be an integer of at least 1 because the root span takes one slot, got #{max_spans.inspect}."
201
+ end
202
+ private_class_method :validate_trace_options!
203
+
208
204
  def self.validate_node_options!(capture:, mock_on_replay:)
209
205
  return unless !capture && mock_on_replay
210
206
 
@@ -278,7 +274,7 @@ module Bitfab
278
274
  # When true, `client.replay(...)` returns this span's
279
275
  # historical output instead of executing the wrapped method.
280
276
  def bitfab_span(method_name, trace_function_key: nil, name: nil, type: "custom",
281
- capture_when: "always", mock_on_replay: false)
277
+ capture_when: "always", mock_on_replay: false, finalize: nil, test_run_id: nil)
282
278
  trace_function_key ||= @bitfab_function_key
283
279
  unless trace_function_key
284
280
  raise "No trace function key provided. Pass `trace_function_key:` to `bitfab_span` " \
@@ -288,7 +284,7 @@ module Bitfab
288
284
  # If the method already exists (inline or after-method style), wrap it immediately
289
285
  if method_defined?(method_name) || private_method_defined?(method_name)
290
286
  _bitfab_wrap_method(
291
- method_name, trace_function_key:, name:, type:, capture_when:, mock_on_replay:
287
+ method_name, trace_function_key:, name:, type:, capture_when:, mock_on_replay:, finalize:, test_run_id:
292
288
  )
293
289
  else
294
290
  # Method doesn't exist yet (before-method style) - register for method_added hook
@@ -298,7 +294,9 @@ module Bitfab
298
294
  name:,
299
295
  type:,
300
296
  capture_when:,
301
- mock_on_replay:
297
+ mock_on_replay:,
298
+ finalize:,
299
+ test_run_id:
302
300
  }
303
301
  end
304
302
  end
@@ -307,7 +305,9 @@ module Bitfab
307
305
  # Descendants require no Bitfab declarations and record as function spans.
308
306
  def bitfab_trace(method_name, trace_function_key: nil, name: nil, type: "custom",
309
307
  max_depth: Subtree::DEFAULT_MAX_DEPTH, max_spans: Subtree::DEFAULT_MAX_SPANS,
310
- exclude: [], include_wrappers: false)
308
+ max_captured_subtree_spans: Subtree::DEFAULT_MAX_CAPTURED_SUBTREE_SPANS,
309
+ exclude: [], include_wrappers: false, mock_on_replay_default: false)
310
+ Traceable.send(:validate_trace_options!, max_spans:)
311
311
  trace_function_key ||= @bitfab_function_key
312
312
  unless trace_function_key
313
313
  raise "No trace function key provided. Pass `trace_function_key:` to `bitfab_trace` " \
@@ -320,8 +320,10 @@ module Bitfab
320
320
  type:,
321
321
  max_depth:,
322
322
  max_spans:,
323
+ max_captured_subtree_spans:,
323
324
  exclude:,
324
- include_wrappers:
325
+ include_wrappers:,
326
+ mock_on_replay_default:
325
327
  }
326
328
  if method_defined?(method_name) || private_method_defined?(method_name)
327
329
  _bitfab_trace_method(method_name, **config)
@@ -334,7 +336,7 @@ module Bitfab
334
336
  # EXPERIMENTAL. Configure one method discovered beneath bitfab_trace.
335
337
  # The node inherits the active trace key and never traces by itself.
336
338
  def bitfab_node(method_name, name: nil, type: "custom", capture: true,
337
- test_run_id: nil, mock_on_replay: false, finalize: nil)
339
+ test_run_id: nil, mock_on_replay: nil, finalize: nil)
338
340
  Traceable.send(:validate_node_options!, capture:, mock_on_replay:)
339
341
  config = {name:, type:, capture:, test_run_id:, mock_on_replay:, finalize:}
340
342
  if method_defined?(method_name) || private_method_defined?(method_name)
@@ -361,88 +363,16 @@ module Bitfab
361
363
  end
362
364
  end
363
365
 
364
- def _bitfab_wrap_method(method_name, trace_function_key:, name: nil, type: "custom",
365
- capture_when: "always", mock_on_replay: false)
366
- span_name = name || method_name.to_s
367
- method_name_str = method_name.to_s
368
-
369
- wrapper = Module.new do
370
- define_method(method_name) do |*args, **kwargs, &block|
371
- # If Bitfab was never configured, run the real method untraced
372
- # rather than raising a "not configured" error into the host app.
373
- client = Bitfab.client_or_nil
374
- return super(*args, **kwargs, &block) unless client
375
-
376
- client.send(:execute_span,
377
- trace_function_key:,
378
- span_name:,
379
- span_type: type,
380
- function_name: method_name_str,
381
- args:,
382
- kwargs:,
383
- capture_when:,
384
- mock_on_replay:,
385
- explicit_span_receiver: self,
386
- explicit_span_method_name: method_name) do
387
- super(*args, **kwargs, &block)
388
- end
389
- end
390
- end
391
-
392
- wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
393
- wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
394
- prepend(wrapper)
366
+ def _bitfab_wrap_method(method_name, **options)
367
+ Traceable.wrap(self, method_name, **options)
395
368
  end
396
369
 
397
- def _bitfab_trace_method(method_name, trace_function_key:, name: nil, type: "custom",
398
- max_depth: Subtree::DEFAULT_MAX_DEPTH, max_spans: Subtree::DEFAULT_MAX_SPANS,
399
- exclude: [], include_wrappers: false)
400
- source = Traceable.send(:source_of, self, method_name)
401
- capture_config = Subtree.prepare(root_source: source, exclude:)
402
- span_name = name || method_name.to_s
403
- method_name_str = method_name.to_s
404
-
405
- wrapper = Module.new do
406
- define_method(method_name) do |*args, **kwargs, &block|
407
- if Subtree.active_trace?(trace_function_key)
408
- next super(*args, **kwargs, &block)
409
- end
410
-
411
- client = Bitfab.client_or_nil
412
- return super(*args, **kwargs, &block) unless client
413
-
414
- client.send(
415
- :execute_span,
416
- trace_function_key:,
417
- span_name:,
418
- span_type: type,
419
- function_name: method_name_str,
420
- args:,
421
- kwargs:,
422
- independent_root: true
423
- ) do
424
- Subtree.capture(
425
- client:,
426
- trace_function_key:,
427
- config: capture_config,
428
- max_depth:,
429
- max_spans:,
430
- include_wrappers:,
431
- root_receiver: self,
432
- root_method_name: method_name
433
- ) { super(*args, **kwargs, &block) }
434
- end
435
- end
436
- end
437
-
438
- wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
439
- wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
440
- Traceable.send(:preserve_visibility, self, wrapper, method_name)
441
- prepend(wrapper)
370
+ def _bitfab_trace_method(method_name, **options)
371
+ Traceable.trace(self, method_name, **options)
442
372
  end
443
373
 
444
374
  def _bitfab_node_method(method_name, name: nil, type: "custom", capture: true,
445
- test_run_id: nil, mock_on_replay: false, finalize: nil)
375
+ test_run_id: nil, mock_on_replay: nil, finalize: nil)
446
376
  Traceable.node(
447
377
  self,
448
378
  method_name,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.51.9"
4
+ VERSION = "0.57.0"
5
5
  end
data/lib/bitfab.rb CHANGED
@@ -14,13 +14,20 @@ require_relative "bitfab/git_state"
14
14
  require_relative "bitfab/span_context"
15
15
  require_relative "bitfab/http_client"
16
16
  require_relative "bitfab/datasets"
17
+ require_relative "bitfab/assertion_categories"
17
18
  require_relative "bitfab/traces"
19
+ require_relative "bitfab/assertions"
20
+ require_relative "bitfab/labels"
21
+ require_relative "bitfab/graders"
18
22
  require_relative "bitfab/mock_override"
19
23
  require_relative "bitfab/replay"
20
24
  require_relative "bitfab/replay_branch"
21
25
  require_relative "bitfab/client"
26
+ require_relative "bitfab/seed"
27
+ require_relative "bitfab/baml"
22
28
  require_relative "bitfab/traceable"
23
29
  require_relative "bitfab/replay_registry"
30
+ require_relative "bitfab/seed_cli"
24
31
  require_relative "bitfab/replay_cli"
25
32
 
26
33
  module Bitfab
@@ -79,8 +86,8 @@ module Bitfab
79
86
  # @example
80
87
  # Bitfab.configure(api_key: ENV["BITFAB_API_KEY"])
81
88
  #
82
- def configure(api_key: nil, service_url: nil, enabled: nil, strict: false)
83
- @client = Client.new(api_key:, service_url:, enabled:, strict:)
89
+ def configure(api_key: nil, service_url: nil, capture_enabled: nil, enabled: nil, strict: false, simulation_plan: true, timeout: 120, trace_across_threads: nil, env_vars: nil, baml_client: nil, db_snapshot: nil)
90
+ @client = Client.new(api_key:, service_url:, capture_enabled:, enabled:, strict:, simulation_plan:, timeout:, trace_across_threads:, env_vars:, baml_client:, db_snapshot:)
84
91
  end
85
92
 
86
93
  # Returns the global client, raising if not configured.
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.51.9
4
+ version: 0.57.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team
@@ -43,6 +43,20 @@ dependencies:
43
43
  - - "<"
44
44
  - !ruby/object:Gem::Version
45
45
  version: '2'
46
+ - !ruby/object:Gem::Dependency
47
+ name: concurrent-ruby
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '1.3'
53
+ type: :development
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '1.3'
46
60
  - !ruby/object:Gem::Dependency
47
61
  name: rake
48
62
  requirement: !ruby/object:Gem::Requirement
@@ -133,32 +147,49 @@ email:
133
147
  - team@goharvest.ai
134
148
  executables:
135
149
  - bitfab-replay
150
+ - bitfab-seed
136
151
  extensions: []
137
152
  extra_rdoc_files: []
138
153
  files:
139
154
  - README.md
140
155
  - exe/bitfab-replay
156
+ - exe/bitfab-seed
141
157
  - lib/bitfab.rb
158
+ - lib/bitfab/assertion_categories.rb
159
+ - lib/bitfab/assertions.rb
160
+ - lib/bitfab/baml.rb
142
161
  - lib/bitfab/client.rb
143
162
  - lib/bitfab/commit_ref.rb
144
163
  - lib/bitfab/compress.rb
145
164
  - lib/bitfab/constants.rb
146
165
  - lib/bitfab/datasets.rb
147
166
  - lib/bitfab/db_snapshot.rb
167
+ - lib/bitfab/detached_trace.rb
148
168
  - lib/bitfab/env.rb
149
169
  - lib/bitfab/git_command.rb
150
170
  - lib/bitfab/git_state.rb
171
+ - lib/bitfab/graders.rb
151
172
  - lib/bitfab/http_client.rb
173
+ - lib/bitfab/labels.rb
152
174
  - lib/bitfab/mock_override.rb
153
175
  - lib/bitfab/otel.rb
154
176
  - lib/bitfab/payload_budget.rb
155
177
  - lib/bitfab/replay.rb
156
178
  - lib/bitfab/replay_branch.rb
157
179
  - lib/bitfab/replay_cli.rb
180
+ - lib/bitfab/replay_concurrency.rb
181
+ - lib/bitfab/replay_memory.rb
182
+ - lib/bitfab/replay_processes.rb
158
183
  - lib/bitfab/replay_registry.rb
184
+ - lib/bitfab/seed.rb
185
+ - lib/bitfab/seed_cli.rb
186
+ - lib/bitfab/seed_context.rb
159
187
  - lib/bitfab/serialize.rb
188
+ - lib/bitfab/simulation_plan.rb
160
189
  - lib/bitfab/span_context.rb
190
+ - lib/bitfab/span_origin.rb
161
191
  - lib/bitfab/subtree.rb
192
+ - lib/bitfab/thread_propagation.rb
162
193
  - lib/bitfab/timestamp.rb
163
194
  - lib/bitfab/trace_completion.rb
164
195
  - lib/bitfab/traceable.rb