bitfab 0.31.0 → 0.33.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d85c35718e6138d5095d4d20278fdac5f3c6f31858684622cb3a68bad6b5416
4
- data.tar.gz: 1de8519b600a8ea4a29593a82f938669d8aa9e9ec616c95102c0abe6d06045c6
3
+ metadata.gz: fa66ab7393ccfdd162cdeaecee49c7b18efa601244afc8d7830568602968efd2
4
+ data.tar.gz: 29312e2278ad9a39033f82f01a45c20ef12bee1f3ac4b6140c94b22f0ae9dc48
5
5
  SHA512:
6
- metadata.gz: 0c6d45ca5bbaedfd2a162e2d05c4bef6da21b31c482db91d5539fb46b79be936aa4600e2ea5d0875ecf0e608907ee92a028660edbcd08dc367ce942003658538
7
- data.tar.gz: db3e762b1ce6b7cb60f4996f4642d6e2a3d5e6a7e6b886a14c319b98ae198f0f210e7af857fda35c53d4e2a49339283ae929f6338d1bae3f64f9be6b97023acc
6
+ metadata.gz: dda0002685836ac6e381ecbf37430950df3fa7df9bad695e80803bb1c0da432885027be571feb02ba571adbc4c98a927e1a67eb910f3c8784c0bfde18e4b4f75
7
+ data.tar.gz: 183c49cc2d9ed286d3ea94e5505e09d6efdfe1cd2e5d73c31e1d504029643745bb796029395c70dc8afc1b2f165b3ecb84d2798933b806d0c00bd8b483d7a94e
data/lib/bitfab/client.rb CHANGED
@@ -105,6 +105,11 @@ module Bitfab
105
105
  # first matching override wins. Per-call overrides take precedence over
106
106
  # those registered via register_mock_override, and both take precedence
107
107
  # over the base mock strategy. The root span is never overridden.
108
+ # @param db_branch [Hash, nil] passing it at all turns database branching
109
+ # on, so +{}+ enables it with the mirror project's own sizing. Keys
110
+ # +:min_cu+/+:max_cu+ size the branch compute in Neon Compute Units and
111
+ # +:warmup_sql+ warms its cache. Read the resolved branch inside the
112
+ # replayed method with +Bitfab.current_replay_branch+.
108
113
  # @param on_progress [#call, nil] optional callback invoked once per item as
109
114
  # it finishes, with a running-totals hash { completed:, total:, succeeded:,
110
115
  # errored: }. Use it to render replay progress (e.g. a terminal progress
@@ -114,12 +119,12 @@ module Bitfab
114
119
  # @return [Hash] with :items, :test_run_id, :test_run_url
115
120
  def replay(receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, max_concurrency: 10,
116
121
  name: nil, code_change_description: nil, code_change_files: nil, experiment_group_id: nil, dataset_id: nil, grader_ids: nil, mock: "marked",
117
- adapt_inputs: nil, mock_override: nil, environment: nil, db_branch: nil, on_progress: nil)
122
+ adapt_inputs: nil, mock_override: nil, db_branch: nil, on_progress: nil)
118
123
  Replay.run(
119
124
  self, receiver, method_name,
120
125
  trace_function_key:, limit:, trace_ids:, name:, max_concurrency:,
121
126
  code_change_description:, code_change_files:, experiment_group_id:, dataset_id:, grader_ids:, mock:, adapt_inputs:,
122
- mock_override:, environment:, db_branch:,
127
+ mock_override:, db_branch:,
123
128
  on_progress:
124
129
  )
125
130
  end
data/lib/bitfab/replay.rb CHANGED
@@ -58,12 +58,12 @@ module Bitfab
58
58
  ctx[:fetch_span_output] = fetch_span_output if fetch_span_output
59
59
  end
60
60
  # The per-trace DB branch (resolved server-side) and the Bitfab trace ID
61
- # it belongs to ride on the context so ReplayEnvironment can read them
62
- # inside the replayed method.
61
+ # it belongs to ride on the context so ReplayBranch can read them inside
62
+ # the replayed method.
63
63
  #
64
- # ReplayEnvironment also sets ctx[:db_snapshot_accessed] = true the
65
- # first time customer code actually obtains the branch URL for this
66
- # item (via +database_url+ or +snapshot+). Reported on the trace
64
+ # ReplayBranch also sets ctx[:db_snapshot_accessed] = true the first
65
+ # time customer code actually obtains the branch URL for this item
66
+ # (via +database_url+). Reported on the trace
67
67
  # completion inside the +db_snapshot_usage+ record (its +accessed+
68
68
  # field) so the server can distinguish "branch was provisioned and
69
69
  # exposed" from "branch URL was actually consumed". Any future
@@ -120,6 +120,12 @@ module Bitfab
120
120
  # source_trace_id/source_span_id aliases), and returns [new_args, new_kwargs].
121
121
  # Runs per item inside the same rescue as the method, so a raising adapter
122
122
  # sets that item's :error rather than crashing the run.
123
+ # @param db_branch [Hash, nil] passing it at all turns database branching
124
+ # on, so +{}+ enables it with the mirror project's own sizing. Keys
125
+ # +:min_cu+/+:max_cu+ are the branch compute's autoscaling floor and
126
+ # ceiling in Neon Compute Units, and +:warmup_sql+ warms the branch's
127
+ # cache before the replayed method sees it. Read the resolved branch
128
+ # inside the method with +Bitfab.current_replay_branch+.
123
129
  # @param on_progress [#call, nil] optional callback invoked once per item as
124
130
  # it finishes, with a running-totals hash { completed:, total:, succeeded:,
125
131
  # errored:, item: } where item is { trace_id:, original_trace_id:,
@@ -136,7 +142,7 @@ module Bitfab
136
142
  def run(client, receiver, method_name, trace_function_key:, limit: nil, trace_ids: nil, name: nil,
137
143
  max_concurrency: 10, code_change_description: nil, code_change_files: nil, experiment_group_id: nil,
138
144
  dataset_id: nil, grader_ids: nil, mock: "marked",
139
- adapt_inputs: nil, mock_override: nil, environment: nil, db_branch: nil, on_progress: nil)
145
+ adapt_inputs: nil, mock_override: nil, db_branch: nil, on_progress: nil)
140
146
  unless MOCK_STRATEGIES.include?(mock.to_s)
141
147
  raise ArgumentError, "Invalid mock strategy '#{mock}'. Must be one of: #{MOCK_STRATEGIES.join(", ")}"
142
148
  end
@@ -179,7 +185,7 @@ module Bitfab
179
185
  # the count), so it's omitted from the request entirely.
180
186
  effective_limit = trace_ids ? nil : (limit || 5)
181
187
 
182
- include_db_branch_lease = !environment.nil?
188
+ include_db_branch_lease = !db_branch.nil?
183
189
 
184
190
  # An explicit code change always wins; only when the caller passed neither
185
191
  # field do we fall back to the payload the replay wrapper captured from git
@@ -332,8 +338,8 @@ module Bitfab
332
338
  return nil if db_branch.nil? || db_branch.empty?
333
339
 
334
340
  # Symbol or string keys, matching normalize_code_change_files: a hash
335
- # loaded from JSON or YAML would otherwise be read as empty, leaving the
336
- # branch on the mirror's defaults with no error to notice.
341
+ # assembled from JSON or YAML would otherwise be read as empty, leaving
342
+ # the branch on the mirror's defaults with no error to notice.
337
343
  min_cu = db_branch[:min_cu] || db_branch["min_cu"]
338
344
  max_cu = db_branch[:max_cu] || db_branch["max_cu"]
339
345
  warmup_sql = db_branch[:warmup_sql] || db_branch["warmup_sql"]
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bitfab
4
+ # The database branch a single replay item runs against.
5
+ #
6
+ # +Bitfab.current_replay_branch+ hands you one inside a replayed method when
7
+ # the source trace carried a DB snapshot reference and the Bitfab service
8
+ # resolved a branch from it. Outside a replay item, or when no branch was
9
+ # resolved, that reader returns nil and your code keeps reading
10
+ # +ENV["DATABASE_URL"]+ the normal way.
11
+ #
12
+ # Immutable and scoped to one item: the reader builds it from the thread-local
13
+ # replay context, so parallel replay items each see their own branch and no
14
+ # lease state lives on a long-lived object.
15
+ #
16
+ # Internally the resolved per-item state is a DB branch lease (the SDK/server
17
+ # protocol term). Its useful fields are exposed directly here so customer code
18
+ # never sees the word.
19
+ class ReplayBranch
20
+ # When this branch's URL stops being valid. ISO-8601.
21
+ attr_reader :expires_at
22
+
23
+ # Deep link to the branch in the provider console, if available.
24
+ attr_reader :provider_console_url
25
+
26
+ # True if the branch is read-only. Use it to skip write operations during
27
+ # replay when the provider returned a read-only lease.
28
+ attr_reader :read_only
29
+
30
+ # The branch's region, e.g. "aws-us-east-1". A compute runs in its project's
31
+ # region, so a replay runner elsewhere pays that round trip on every query.
32
+ attr_reader :region
33
+
34
+ # The historical trace ID that produced the input for this replay item.
35
+ attr_reader :trace_id
36
+
37
+ # Built by Bitfab.current_replay_branch; never constructed by callers.
38
+ def initialize(lease, trace_id, context)
39
+ @expires_at = lease["expiresAt"]
40
+ @provider_console_url = lease["providerConsoleUrl"]
41
+ @read_only = lease["readOnly"]
42
+ @region = lease["region"]
43
+ @trace_id = trace_id
44
+ @url = lease["databaseUrl"]
45
+ @context = context
46
+ freeze
47
+ end
48
+
49
+ # Connection string for this item's branch. Point your database client at it
50
+ # instead of the live database for the duration of the replayed call.
51
+ #
52
+ # Reading it records on the trace that the replayed code obtained the branch
53
+ # URL, which is what separates "a branch was provisioned" from "the branch
54
+ # was actually used". The other readers inspect the lease without exposing
55
+ # the connection string, so they deliberately do not record anything.
56
+ def database_url
57
+ @context[:db_snapshot_accessed] = true
58
+ @url
59
+ end
60
+
61
+ # Redact the connection string so a logged or inspected branch cannot leak it.
62
+ def inspect
63
+ "#<Bitfab::ReplayBranch trace_id=#{@trace_id.inspect} " \
64
+ "expires_at=#{@expires_at.inspect} region=#{@region.inspect} " \
65
+ "read_only=#{@read_only.inspect}>"
66
+ end
67
+ end
68
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bitfab
4
- VERSION = "0.31.0"
4
+ VERSION = "0.33.0"
5
5
  end
data/lib/bitfab.rb CHANGED
@@ -11,7 +11,7 @@ require_relative "bitfab/span_context"
11
11
  require_relative "bitfab/http_client"
12
12
  require_relative "bitfab/mock_override"
13
13
  require_relative "bitfab/replay"
14
- require_relative "bitfab/replay_environment"
14
+ require_relative "bitfab/replay_branch"
15
15
  require_relative "bitfab/client"
16
16
  require_relative "bitfab/traceable"
17
17
 
@@ -102,6 +102,34 @@ module Bitfab
102
102
  CurrentSpan.new(entry)
103
103
  end
104
104
 
105
+ # Get the database branch the current replay item is running against.
106
+ #
107
+ # Call this from inside a method being replayed with
108
+ # +client.replay(db_branch: ...)+ and point your database client at
109
+ # +branch.database_url+ so the replay reads the data as it was at trace
110
+ # time:
111
+ #
112
+ # branch = Bitfab.current_replay_branch
113
+ # url = branch ? branch.database_url : ENV["DATABASE_URL"]
114
+ #
115
+ # @return [ReplayBranch, nil] nil outside a replay item, and for an item
116
+ # whose source trace carried no DB snapshot reference
117
+ def current_replay_branch
118
+ ctx = ReplayContext.current
119
+ return nil unless ctx
120
+
121
+ lease = ctx[:db_branch_lease]
122
+ return nil unless lease
123
+
124
+ # Surface the Bitfab trace ID (what the customer sees in the dashboard),
125
+ # not the external one. Falling back to the external ID keeps replays from
126
+ # external sources working until that path is fully wired.
127
+ trace_id = ctx[:source_bitfab_trace_id] || ctx[:input_source_trace_id]
128
+ return nil unless trace_id
129
+
130
+ ReplayBranch.new(lease, trace_id, ctx)
131
+ end
132
+
105
133
  # Get a handle to the current active trace.
106
134
  #
107
135
  # Call this from inside a traced method to get a trace handle that allows
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.31.0
4
+ version: 0.33.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Harvest Team
@@ -123,7 +123,7 @@ files:
123
123
  - lib/bitfab/http_client.rb
124
124
  - lib/bitfab/mock_override.rb
125
125
  - lib/bitfab/replay.rb
126
- - lib/bitfab/replay_environment.rb
126
+ - lib/bitfab/replay_branch.rb
127
127
  - lib/bitfab/serialize.rb
128
128
  - lib/bitfab/span_context.rb
129
129
  - lib/bitfab/traceable.rb
@@ -1,118 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Bitfab
4
- # Per-trace environment exposed to customer code during replay.
5
- #
6
- # The customer instantiates one +ReplayEnvironment+ and passes it to
7
- # +client.replay(environment: ...)+. Inside the replayed method they read
8
- # +env.database_url+ (and friends) to pick up the per-trace branch URL the
9
- # Bitfab service resolved from the source trace's snapshot reference.
10
- #
11
- # Outside replay, reading +env.database_url+ raises. Customer code uses the
12
- # env only on the replay path; live request code keeps reading
13
- # +ENV["DATABASE_URL"]+ the normal way.
14
- #
15
- # Concurrency-safe: the readers resolve through the thread-local replay
16
- # context, so each in-flight replay item sees its own per-trace values even
17
- # when the SDK runs items across worker threads.
18
- #
19
- # Internally the resolved per-item state is a DB branch lease (the SDK <->
20
- # server protocol term). We expose its useful fields directly here so
21
- # customer code never sees the word.
22
- class ReplayEnvironment
23
- # The per-trace branch URL for the item currently being replayed.
24
- # Raises if read outside a replay item.
25
- def database_url
26
- snap = require_snapshot
27
- mark_accessed
28
- snap.fetch(:database_url)
29
- end
30
-
31
- # When the per-trace branch URL stops being valid. ISO-8601.
32
- def expires_at
33
- require_snapshot.fetch(:expires_at)
34
- end
35
-
36
- # Deep link to the branch in the provider console, if available.
37
- def provider_console_url
38
- require_snapshot[:provider_console_url]
39
- end
40
-
41
- # True if the branch is read-only. Customer code can use this to skip write
42
- # operations during replay when the provider returned a read-only lease.
43
- def read_only
44
- require_snapshot[:read_only]
45
- end
46
-
47
- # The branch's region, e.g. "aws-us-east-1". A compute runs in its
48
- # project's region, so a replay runner elsewhere pays that round trip on
49
- # every query.
50
- def region
51
- require_snapshot[:region]
52
- end
53
-
54
- # The historical trace ID that produced the input for this replay item.
55
- def trace_id
56
- require_snapshot.fetch(:trace_id)
57
- end
58
-
59
- # True when read inside a replay item that has a resolved branch.
60
- def active?
61
- !read_snapshot.nil?
62
- end
63
-
64
- # Non-raising variant for callers that handle the inactive case. Returns a
65
- # symbol-keyed hash or nil.
66
- def snapshot
67
- snap = read_snapshot
68
- mark_accessed if snap
69
- snap
70
- end
71
-
72
- private
73
-
74
- # Record on the replay context that customer code obtained the branch
75
- # URL. Only +database_url+ and +snapshot+ count: +active?+, +read_only+
76
- # and friends inspect the lease without exposing the connection string,
77
- # so they don't prove the replayed code could have connected to the
78
- # branch.
79
- def mark_accessed
80
- ctx = ReplayContext.current
81
- return unless ctx && ctx[:db_branch_lease]
82
-
83
- ctx[:db_snapshot_accessed] = true
84
- end
85
-
86
- def read_snapshot
87
- ctx = ReplayContext.current
88
- return nil unless ctx
89
-
90
- lease = ctx[:db_branch_lease]
91
- return nil unless lease
92
-
93
- # Surface the Bitfab trace ID (what the customer sees in the dashboard),
94
- # falling back to the external trace ID only if the Bitfab ID is somehow
95
- # absent: keeps replays from external sources working until the
96
- # source-system path is fully wired.
97
- trace_id = ctx[:source_bitfab_trace_id] || ctx[:input_source_trace_id]
98
- return nil unless trace_id
99
-
100
- {
101
- database_url: lease["databaseUrl"],
102
- expires_at: lease["expiresAt"],
103
- provider_console_url: lease["providerConsoleUrl"],
104
- read_only: lease["readOnly"],
105
- region: lease["region"],
106
- trace_id:
107
- }
108
- end
109
-
110
- def require_snapshot
111
- snap = read_snapshot
112
- return snap if snap
113
-
114
- raise "ReplayEnvironment accessed outside of a replay item. Pass it to " \
115
- "client.replay(environment: ...) and only read it inside the replayed method."
116
- end
117
- end
118
- end