bitfab 0.20.0 → 0.20.2

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: 9624757032370e1e318121cabcf2bfa984c7bcdcd07c9ecd5dcee449fef57a7c
4
- data.tar.gz: c3925bbae804c16d21c3f373ec6cde4de340ee645fc6423505fc0b5d5f5e638e
3
+ metadata.gz: d2428cc39dcd97d154e36ba9792628478e86a4b4dde5101c79d37ebb1635378f
4
+ data.tar.gz: effa0dba40674696ad3d9155615d9d9e2e40a3fe7f9aed9c8ee97516985e13c6
5
5
  SHA512:
6
- metadata.gz: d2c33cd4a3432d79703685f00bb8c8278e2bebbd694b3a695f1dee646680a5af11370f233635f04590eeb0d5cedee850c496b40bee2fc176c34cf22fb721cde8
7
- data.tar.gz: c64be06a07ca8ea807bca23e0f96acb15e1647945b33c3ea731f10c0b52759870b88637d86b680aee07caac7836e3818946f2fb85f0979d83b3ea89ef8ea1704
6
+ metadata.gz: 5f172f0178829a08315ae1caf3de6ff4a22389b2f83dbf449721ca53ca3a466580af00c15bd5279dffc2c9d074aee2afd0e157733f9257987460ef8d101350c9
7
+ data.tar.gz: 9c6af19d4ebac35d13280aeaa89ac6104cbff3020e24813e292e4170c6524016a0df943025f3ced5510345c863e4b86f9f027fe281912e99a6438f863dde9d28
data/lib/bitfab/replay.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "constants"
4
4
  require_relative "serialize"
5
+ require_relative "traceable"
5
6
 
6
7
  module Bitfab
7
8
  # Replay mock strategies. Mirrors the Python and TypeScript SDKs.
@@ -117,6 +118,21 @@ module Bitfab
117
118
  "determines how many traces replay."
118
119
  end
119
120
 
121
+ # Reject a trace_function_key that contradicts the method's declared key.
122
+ # replay() fetches historical traces by trace_function_key but records the
123
+ # replayed spans under the method's own bitfab_span key (via send below),
124
+ # so a mismatch produces an incoherent test run (traces fetched for one
125
+ # function, recorded under another). Only fires when the method's key is
126
+ # introspectable; an untraced method falls through to the persistence
127
+ # check in complete_replay. Mirrors the TypeScript/Python SDKs.
128
+ declared_key = Traceable.trace_function_key_for(receiver, method_name)
129
+ if declared_key && declared_key != trace_function_key
130
+ raise ArgumentError,
131
+ "Method #{method_name} is traced under trace function key '#{declared_key}' but replay was " \
132
+ "called with '#{trace_function_key}'. Pass trace_function_key: '#{declared_key}' to replay it, " \
133
+ "or point at the method traced under '#{trace_function_key}'."
134
+ end
135
+
120
136
  http_client = client.instance_variable_get(:@http_client)
121
137
 
122
138
  # limit is meaningless with explicit trace_ids (the ID list determines
@@ -154,6 +170,10 @@ module Bitfab
154
170
  # loudly.
155
171
  complete_response = http_client.complete_replay(test_run_id)
156
172
  trace_id_map = complete_response&.dig("traceIds")
173
+ # Per-replay-trace token usage keyed by server trace id: the REPLAYED
174
+ # run's tokens (span-aggregated server-side), used below to fill each
175
+ # item's :tokens.
176
+ replay_tokens = complete_response&.dig("tokens") || {}
157
177
 
158
178
  if trace_id_map.nil?
159
179
  # Older servers don't return the mapping. Preserve the legacy
@@ -186,6 +206,9 @@ module Bitfab
186
206
  completed_count += 1
187
207
  missing << item[:trace_id] if mapped.nil?
188
208
  end
209
+ # Pull this item's replayed-run tokens by its server trace id, before
210
+ # :trace_id is overwritten with that id below.
211
+ item[:tokens] = normalize_tokens(replay_tokens[mapped]) if mapped
189
212
  item[:trace_id] = mapped
190
213
  end
191
214
  if missing.any?
@@ -370,27 +393,34 @@ module Bitfab
370
393
  }
371
394
  end
372
395
 
373
- # Pull durationMs / tokens / model from the start-replay server item.
374
- # Normalizes to symbol-keyed tokens hash and nil-safe defaults so older
375
- # servers without these fields still produce a consistent shape.
396
+ # Pull durationMs / model from the start-replay server item. Nil-safe
397
+ # defaults so older servers without these fields still produce a consistent
398
+ # shape. Tokens are intentionally NOT read from the start item (it carries
399
+ # the ORIGINAL trace's tokens); the replayed run's tokens are filled in by
400
+ # run() from the complete-replay response once spans are aggregated
401
+ # server-side, and stay nil here and on older servers.
376
402
  def extract_server_item_metrics(server_item)
377
- raw_tokens = server_item["tokens"]
378
- tokens = if raw_tokens.is_a?(Hash)
379
- {
380
- input: raw_tokens["input"],
381
- output: raw_tokens["output"],
382
- cached: raw_tokens["cached"],
383
- total: raw_tokens["total"]
384
- }
385
- end
386
-
387
403
  {
388
404
  duration_ms: server_item["durationMs"],
389
- tokens:,
405
+ tokens: nil,
390
406
  model: server_item["model"]
391
407
  }
392
408
  end
393
409
 
410
+ # Normalize a complete-replay tokens hash (string-keyed JSON) into the
411
+ # symbol-keyed shape the replay item exposes. Nil when the server reported
412
+ # no token data for this trace.
413
+ def normalize_tokens(raw_tokens)
414
+ return nil unless raw_tokens.is_a?(Hash)
415
+
416
+ {
417
+ input: raw_tokens["input"],
418
+ output: raw_tokens["output"],
419
+ cached: raw_tokens["cached"],
420
+ total: raw_tokens["total"]
421
+ }
422
+ end
423
+
394
424
  # Execute a single replay item: deserialize inputs, call method with replay context.
395
425
  def execute_item(item, receiver, method_name, test_run_id, input_source_span_id = nil, metrics = {},
396
426
  input_source_trace_id: nil, mock_strategy: "none", mock_tree: nil, adapt_inputs: nil, adapt_ctx: nil,
@@ -69,9 +69,43 @@ module Bitfab
69
69
  end
70
70
  end
71
71
 
72
+ wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
73
+ wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
72
74
  klass.prepend(wrapper)
73
75
  end
74
76
 
77
+ # Resolve the trace function key a traced method records spans under, or
78
+ # nil if the method carries no Bitfab span wrapper. Walks the receiver's
79
+ # ancestor chain for the wrapper module that bitfab_span / Traceable.wrap
80
+ # prepended. Instances resolve via the class. A Class/Module receiver
81
+ # resolves either way: singleton-class ancestors first (class/module
82
+ # methods wrapped on the singleton class), then the class's own ancestors
83
+ # (methods whose wrapper was prepended onto the class itself), so a
84
+ # class-method replay is guarded regardless of which surface carries the
85
+ # wrapper. Singleton-first keeps the method actually dispatched by
86
+ # `receiver.send` (a class method shadows a same-named instance wrapper).
87
+ #
88
+ # replay() uses this to reject a trace_function_key that contradicts the
89
+ # method's declared key: a mismatch fetches one function's historical
90
+ # traces but re-records the replay under the method's own key, producing
91
+ # an incoherent test run. Mirrors the TypeScript and Python SDKs, which
92
+ # throw on the same key mismatch.
93
+ def self.trace_function_key_for(receiver, method_name)
94
+ name = method_name.to_s
95
+ ancestors = if receiver.is_a?(Module)
96
+ receiver.singleton_class.ancestors + receiver.ancestors
97
+ else
98
+ receiver.class.ancestors
99
+ end
100
+ ancestors.each do |mod|
101
+ next unless mod.instance_variable_defined?(:@bitfab_span_method)
102
+ next unless mod.instance_variable_get(:@bitfab_span_method) == name
103
+
104
+ return mod.instance_variable_get(:@bitfab_span_key)
105
+ end
106
+ nil
107
+ end
108
+
75
109
  module ClassMethods
76
110
  # Set the trace function key for this class.
77
111
  # All spans declared in this class will be grouped under this key.
@@ -152,6 +186,8 @@ module Bitfab
152
186
  end
153
187
  end
154
188
 
189
+ wrapper.instance_variable_set(:@bitfab_span_method, method_name_str)
190
+ wrapper.instance_variable_set(:@bitfab_span_key, trace_function_key)
155
191
  prepend(wrapper)
156
192
  end
157
193
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.20.0"
4
+ VERSION = "0.20.2"
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.20.0
4
+ version: 0.20.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team