bitfab 0.32.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: 6c30821c7a2d174dacfae3c40d838cf9b37d2f71e07be3b95aa385507cd26867
4
- data.tar.gz: f041e1f63eedc9397c55d70f635ca75d0f365c7b6a970f943b6a282bcc4b10e9
3
+ metadata.gz: fa66ab7393ccfdd162cdeaecee49c7b18efa601244afc8d7830568602968efd2
4
+ data.tar.gz: 29312e2278ad9a39033f82f01a45c20ef12bee1f3ac4b6140c94b22f0ae9dc48
5
5
  SHA512:
6
- metadata.gz: 8427f0d34ca1fbafcbd1d8582da7d24fba552635e23fdc9cba0a5afe1198a3f0f712c1c7c3a25b4229abc441c8fc4057bc7bb284f6e830173213d4194410d699
7
- data.tar.gz: e4472dd0149b0a604439da779a5bae825f8313b41f2270701ab77b1ee46baf286c636d6022ce0df65cbca995525a0a53418ba4f2d839db582be7a17be41c9c15
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, 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:,
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, 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
@@ -205,7 +211,7 @@ module Bitfab
205
211
  include_db_branch_lease:,
206
212
  dataset_id:,
207
213
  grader_ids:,
208
- db_branch_settings: db_branch_settings(environment)
214
+ db_branch_settings: db_branch_settings(db_branch)
209
215
  )
210
216
  test_run_id = replay_data["testRunId"]
211
217
  test_run_url = replay_data["testRunUrl"]
@@ -320,7 +326,7 @@ module Bitfab
320
326
  CC_MAX_FILE_BYTES = 500_000
321
327
  CC_MAX_TOTAL_BYTES = 2_000_000
322
328
 
323
- # Convert the environment's branch options to the wire shape, or nil when
329
+ # Convert the caller's +db_branch+ options to the wire shape, or nil when
324
330
  # nothing was set. Dropping the key entirely keeps the request identical to
325
331
  # what older SDKs send.
326
332
  #
@@ -328,8 +334,7 @@ module Bitfab
328
334
  # ceiling in Neon Compute Units (equal values pin the size, keeping items
329
335
  # comparable); :warmup_sql is appended to the branch's readiness check so it
330
336
  # warms the cache before the replayed function sees the lease.
331
- def db_branch_settings(environment)
332
- db_branch = environment&.db_branch
337
+ def db_branch_settings(db_branch)
333
338
  return nil if db_branch.nil? || db_branch.empty?
334
339
 
335
340
  # Symbol or string keys, matching normalize_code_change_files: a hash
@@ -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.32.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.32.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,148 +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
- # The constructor takes how each branch should be sized and warmed. That
20
- # lives here rather than on the replay arguments because the settings only
21
- # mean anything for a replay that has an environment.
22
- #
23
- # Internally the resolved per-item state is a DB branch lease (the SDK <->
24
- # server protocol term). We expose its useful fields directly here so
25
- # customer code never sees the word.
26
- class ReplayEnvironment
27
- # The branch settings this environment was constructed with, as a
28
- # symbol-keyed hash, readable anywhere. Every other reader reports the
29
- # lease the server resolved and is therefore replay-only.
30
- attr_reader :db_branch
31
-
32
- # @param min_cu [Numeric, nil] autoscaling floor for the branch's compute,
33
- # in Neon Compute Units (0.25 to 56). Omit to keep the mirror project's
34
- # own default. Raise it when the mirror is provisioned smaller than the
35
- # database it stands in for, so replay latency reflects your code rather
36
- # than a cold, undersized branch.
37
- # @param max_cu [Numeric, nil] autoscaling ceiling, in Neon Compute Units.
38
- # Equal to +min_cu+ pins the size, which keeps items comparable:
39
- # otherwise a later item can run against an endpoint that already scaled
40
- # up and post a better number for the same code.
41
- # @param warmup_sql [String, nil] SQL that warms the branch's cache. The
42
- # server appends it to the branch's readiness check, so it runs BEFORE
43
- # your method sees the lease and its time is not charged to the replayed
44
- # call. Invalid SQL fails the lease rather than silently leaving the
45
- # branch cold.
46
- def initialize(min_cu: nil, max_cu: nil, warmup_sql: nil)
47
- @db_branch = {}
48
- @db_branch[:min_cu] = min_cu unless min_cu.nil?
49
- @db_branch[:max_cu] = max_cu unless max_cu.nil?
50
- @db_branch[:warmup_sql] = warmup_sql unless warmup_sql.nil?
51
- end
52
-
53
- # The per-trace branch URL for the item currently being replayed.
54
- # Raises if read outside a replay item.
55
- def database_url
56
- snap = require_snapshot
57
- mark_accessed
58
- snap.fetch(:database_url)
59
- end
60
-
61
- # When the per-trace branch URL stops being valid. ISO-8601.
62
- def expires_at
63
- require_snapshot.fetch(:expires_at)
64
- end
65
-
66
- # Deep link to the branch in the provider console, if available.
67
- def provider_console_url
68
- require_snapshot[:provider_console_url]
69
- end
70
-
71
- # True if the branch is read-only. Customer code can use this to skip write
72
- # operations during replay when the provider returned a read-only lease.
73
- def read_only
74
- require_snapshot[:read_only]
75
- end
76
-
77
- # The branch's region, e.g. "aws-us-east-1". A compute runs in its
78
- # project's region, so a replay runner elsewhere pays that round trip on
79
- # every query.
80
- def region
81
- require_snapshot[:region]
82
- end
83
-
84
- # The historical trace ID that produced the input for this replay item.
85
- def trace_id
86
- require_snapshot.fetch(:trace_id)
87
- end
88
-
89
- # True when read inside a replay item that has a resolved branch.
90
- def active?
91
- !read_snapshot.nil?
92
- end
93
-
94
- # Non-raising variant for callers that handle the inactive case. Returns a
95
- # symbol-keyed hash or nil.
96
- def snapshot
97
- snap = read_snapshot
98
- mark_accessed if snap
99
- snap
100
- end
101
-
102
- private
103
-
104
- # Record on the replay context that customer code obtained the branch
105
- # URL. Only +database_url+ and +snapshot+ count: +active?+, +read_only+
106
- # and friends inspect the lease without exposing the connection string,
107
- # so they don't prove the replayed code could have connected to the
108
- # branch.
109
- def mark_accessed
110
- ctx = ReplayContext.current
111
- return unless ctx && ctx[:db_branch_lease]
112
-
113
- ctx[:db_snapshot_accessed] = true
114
- end
115
-
116
- def read_snapshot
117
- ctx = ReplayContext.current
118
- return nil unless ctx
119
-
120
- lease = ctx[:db_branch_lease]
121
- return nil unless lease
122
-
123
- # Surface the Bitfab trace ID (what the customer sees in the dashboard),
124
- # falling back to the external trace ID only if the Bitfab ID is somehow
125
- # absent: keeps replays from external sources working until the
126
- # source-system 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
- {
131
- database_url: lease["databaseUrl"],
132
- expires_at: lease["expiresAt"],
133
- provider_console_url: lease["providerConsoleUrl"],
134
- read_only: lease["readOnly"],
135
- region: lease["region"],
136
- trace_id:
137
- }
138
- end
139
-
140
- def require_snapshot
141
- snap = read_snapshot
142
- return snap if snap
143
-
144
- raise "ReplayEnvironment accessed outside of a replay item. Pass it to " \
145
- "client.replay(environment: ...) and only read it inside the replayed method."
146
- end
147
- end
148
- end