bitfab 0.75.1 → 0.75.3
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/assertions.rb +19 -42
- data/lib/bitfab/client.rb +2 -2
- data/lib/bitfab/error_chain.rb +83 -0
- data/lib/bitfab/replay.rb +4 -8
- data/lib/bitfab/subtree.rb +1 -1
- data/lib/bitfab/version.rb +1 -1
- data/lib/bitfab.rb +1 -0
- 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: 0e8124c4242b339613c4f4b714a68305748f2c32e2d894fd884250bc8c1e4036
|
|
4
|
+
data.tar.gz: 9ed475b72f8f55b58334af9fe4feae30b07940aa9847c763fbccba227e21e41c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7b1356376e48cbaf2a8889fdf7c26945ed21050035603ae480e00d6da313acff4292c981d7f9be751b3dcfd37aa954d5ab6619d492a4f0547f8af5f99faddfdb
|
|
7
|
+
data.tar.gz: 15239246da620b92a67d87aa731974b22efe17fa97f2f25103491454a6ddd64327225a3b12f92f6df9ebc1770aceb82a7eb090a938ec31b95d32b0ba63ebd37e
|
data/lib/bitfab/assertions.rb
CHANGED
|
@@ -4,11 +4,9 @@ require "uri"
|
|
|
4
4
|
|
|
5
5
|
module Bitfab
|
|
6
6
|
class Traces
|
|
7
|
-
CODE_DRAFT_SAVE_TIMEOUT_SECONDS = 300
|
|
8
|
-
CODED_KINDS = ["code", "hybrid", :code, :hybrid].freeze
|
|
9
7
|
CATEGORY_ID_KEYS = ["category_id", :category_id].freeze
|
|
10
8
|
DEPRECATED_CATEGORY_ID_KEYS = ["category_assertion_id", :category_assertion_id].freeze
|
|
11
|
-
private_constant :
|
|
9
|
+
private_constant :CATEGORY_ID_KEYS, :DEPRECATED_CATEGORY_ID_KEYS
|
|
12
10
|
|
|
13
11
|
def initialize(http_client)
|
|
14
12
|
@http_client = http_client
|
|
@@ -31,31 +29,23 @@ module Bitfab
|
|
|
31
29
|
# pass nil to clear it. The deprecated "category_assertion_id" is still
|
|
32
30
|
# accepted and sent as "category_id"; "category_id" wins when both are set.
|
|
33
31
|
#
|
|
34
|
-
# "
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
# the guided rewrite (unless skipped), code of that kind is written and run
|
|
52
|
-
# on the trace, and the save waits for both, so that entry's "rewrite" is
|
|
53
|
-
# already final. Its returned "codeDraft" hash says what happened:
|
|
54
|
-
# "requestedKind", "status" ("written", or "not_written" when no working
|
|
55
|
-
# code could be written and the assertion was saved as "judge" without
|
|
56
|
-
# code), and "reason" (why not, or nil). Every other entry's "codeDraft"
|
|
57
|
-
# is nil. At most 10 assertions per save may ask for code this way, and a
|
|
58
|
-
# save that asks for any waits up to five minutes.
|
|
32
|
+
# "code" is optional code that decides the assertion on a replay without
|
|
33
|
+
# the LLM judge: {"source" => ..., "spanNames" => [...]}. Send a "code"
|
|
34
|
+
# hash to set it, "code" => nil to remove it, or omit "code" to keep the
|
|
35
|
+
# current code. "source" is the body of an async TypeScript (or plain
|
|
36
|
+
# JavaScript) function called with spans (each {name, index, hasData}, in
|
|
37
|
+
# start order, without its data), input, and output. It reads span data
|
|
38
|
+
# with await named("span name") or await span.data(), with one second of
|
|
39
|
+
# compute and the data of at most 50 spans loaded within ten seconds. It
|
|
40
|
+
# returns "pass" or "fail", which decides the verdict with no judge call,
|
|
41
|
+
# or "defer" to hand the replay to the LLM judge, or {result, reason}. A
|
|
42
|
+
# defer may carry what the code found for the judge to read alongside the
|
|
43
|
+
# run: {result: "defer", reason, data}. A thrown error counts as "defer"
|
|
44
|
+
# with no data. "spanNames" lists every span a correct run is expected to
|
|
45
|
+
# produce. The code is run on the trace before saving and refused when it
|
|
46
|
+
# throws, times out, or returns anything other than a result. Returned
|
|
47
|
+
# assertions carry "code" (nil when the assertion has none) and
|
|
48
|
+
# "codeBrokenReason", which says why the code could not run on a replay.
|
|
59
49
|
#
|
|
60
50
|
# By default, after the save, Bitfab checks each assertion's text against its
|
|
61
51
|
# trace. When the text bundles two claims, adds a condition, points at a
|
|
@@ -90,8 +80,7 @@ module Bitfab
|
|
|
90
80
|
"updates" => updates.map { |update| with_new_category_key(update) },
|
|
91
81
|
"source" => source,
|
|
92
82
|
"guidedRewrite" => !skip_guided_rewrite
|
|
93
|
-
}
|
|
94
|
-
timeout: save_timeout(updates)
|
|
83
|
+
}
|
|
95
84
|
)["assertions"]
|
|
96
85
|
saved.is_a?(Array) ? saved.map { |assertion| with_both_category_keys(assertion) } : saved
|
|
97
86
|
end
|
|
@@ -102,18 +91,6 @@ module Bitfab
|
|
|
102
91
|
|
|
103
92
|
private
|
|
104
93
|
|
|
105
|
-
def save_timeout(updates)
|
|
106
|
-
asks = updates.any? do |update|
|
|
107
|
-
(update["assertions"] || update[:assertions] || []).any? { |assertion| asks_bitfab_to_write_code?(assertion) }
|
|
108
|
-
end
|
|
109
|
-
asks ? CODE_DRAFT_SAVE_TIMEOUT_SECONDS : nil
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
def asks_bitfab_to_write_code?(assertion)
|
|
113
|
-
kind = assertion["kind"] || assertion[:kind]
|
|
114
|
-
CODED_KINDS.include?(kind) && !assertion.key?("code") && !assertion.key?(:code)
|
|
115
|
-
end
|
|
116
|
-
|
|
117
94
|
def assertions_path(trace_id, suffix = "")
|
|
118
95
|
"/api/sdk/traces/#{URI.encode_www_form_component(trace_id).gsub("+", "%20")}/assertions#{suffix}"
|
|
119
96
|
end
|
data/lib/bitfab/client.rb
CHANGED
|
@@ -743,7 +743,7 @@ module Bitfab
|
|
|
743
743
|
end
|
|
744
744
|
end
|
|
745
745
|
rescue => e
|
|
746
|
-
error = e
|
|
746
|
+
error = ErrorChain.describe(e)
|
|
747
747
|
complete.call(result, error)
|
|
748
748
|
raise
|
|
749
749
|
end
|
|
@@ -944,7 +944,7 @@ module Bitfab
|
|
|
944
944
|
end
|
|
945
945
|
complete.call(yielded, nil)
|
|
946
946
|
rescue => e
|
|
947
|
-
complete.call(yielded, e
|
|
947
|
+
complete.call(yielded, ErrorChain.describe(e))
|
|
948
948
|
raise
|
|
949
949
|
ensure
|
|
950
950
|
SpanContext.stack.pop
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bitfab
|
|
4
|
+
# Reads the chain of causes below an exception. A database driver error
|
|
5
|
+
# usually reaches a traced method wrapped one or two levels down, so
|
|
6
|
+
# recording only the top message makes a connection timeout read as a broken
|
|
7
|
+
# query. Spans and replay results carry the deepest cause too.
|
|
8
|
+
module ErrorChain
|
|
9
|
+
# How many causes below an exception are followed.
|
|
10
|
+
MAX_DEPTH = 10
|
|
11
|
+
CODE_METHODS = %i[code sqlstate errno].freeze
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
# The causes below `error`, nearest first, stopping at the depth limit or
|
|
16
|
+
# at a cause already seen.
|
|
17
|
+
def causes(error)
|
|
18
|
+
found = []
|
|
19
|
+
seen = {error.object_id => true}
|
|
20
|
+
current = error
|
|
21
|
+
while found.length < MAX_DEPTH
|
|
22
|
+
cause = current.cause
|
|
23
|
+
break if cause.nil? || seen[cause.object_id]
|
|
24
|
+
|
|
25
|
+
found << cause
|
|
26
|
+
seen[cause.object_id] = true
|
|
27
|
+
current = cause
|
|
28
|
+
end
|
|
29
|
+
found
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The exception's message, followed by the deepest cause's message when it
|
|
33
|
+
# wraps another exception, e.g. `Failed query: ... (caused by: connection
|
|
34
|
+
# timeout)`. The cause is left off when the message already contains it.
|
|
35
|
+
def describe(error)
|
|
36
|
+
message = error.message
|
|
37
|
+
deepest = causes(error).last
|
|
38
|
+
return message if deepest.nil?
|
|
39
|
+
|
|
40
|
+
deepest_message = deepest.message.to_s.strip
|
|
41
|
+
return message if deepest_message.empty? || message.include?(deepest_message)
|
|
42
|
+
|
|
43
|
+
"#{message} (caused by: #{deepest_message})"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# An exception as a JSON record: type, message, backtrace, the driver's
|
|
47
|
+
# code when it has one, and its causes nested under `cause` down to the
|
|
48
|
+
# depth limit. The block adds fields a specific exception type carries.
|
|
49
|
+
def serialize(error, &extra)
|
|
50
|
+
serialize_at(error, 1, {}, &extra)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# `seen` is created fresh by `serialize` for each top-level call, so one
|
|
54
|
+
# call never skips a cause another call already recorded.
|
|
55
|
+
def serialize_at(error, depth, seen, &extra)
|
|
56
|
+
seen[error.object_id] = true
|
|
57
|
+
serialized = {type: error.class.name, message: error.message, backtrace: error.backtrace}
|
|
58
|
+
code = error_code(error)
|
|
59
|
+
serialized[:code] = code unless code.nil?
|
|
60
|
+
serialized.merge!(extra.call(error)) if extra
|
|
61
|
+
cause = error.cause
|
|
62
|
+
if cause && !seen[cause.object_id] && depth < MAX_DEPTH
|
|
63
|
+
serialized[:cause] = serialize_at(cause, depth + 1, seen, &extra)
|
|
64
|
+
end
|
|
65
|
+
serialized
|
|
66
|
+
end
|
|
67
|
+
private_class_method :serialize_at
|
|
68
|
+
|
|
69
|
+
def error_code(error)
|
|
70
|
+
CODE_METHODS.each do |name|
|
|
71
|
+
next unless error.respond_to?(name)
|
|
72
|
+
|
|
73
|
+
value = begin
|
|
74
|
+
error.public_send(name)
|
|
75
|
+
rescue
|
|
76
|
+
nil
|
|
77
|
+
end
|
|
78
|
+
return value if value.is_a?(String) || value.is_a?(Integer)
|
|
79
|
+
end
|
|
80
|
+
nil
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
data/lib/bitfab/replay.rb
CHANGED
|
@@ -160,13 +160,9 @@ module Bitfab
|
|
|
160
160
|
def json_safe(value)
|
|
161
161
|
case value
|
|
162
162
|
when Exception
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
serialized[:code] = value.code
|
|
166
|
-
serialized[:original_trace_id] = value.original_trace_id
|
|
167
|
-
serialized[:cause] = json_safe(value.cause) if value.cause
|
|
163
|
+
ErrorChain.serialize(value) do |error|
|
|
164
|
+
error.is_a?(DbBranchReplayError) ? {original_trace_id: error.original_trace_id} : {}
|
|
168
165
|
end
|
|
169
|
-
serialized
|
|
170
166
|
when Hash
|
|
171
167
|
value.to_h { |key, child| [key, json_safe(child)] }
|
|
172
168
|
when Array
|
|
@@ -177,7 +173,7 @@ module Bitfab
|
|
|
177
173
|
end
|
|
178
174
|
|
|
179
175
|
def replay_item_error_message(error)
|
|
180
|
-
return error
|
|
176
|
+
return ErrorChain.describe(error) unless error.is_a?(DbBranchReplayError)
|
|
181
177
|
|
|
182
178
|
"Replay requested a database branch for trace #{error.original_trace_id} but it " \
|
|
183
179
|
"could not be resolved (#{error.code}): #{error.message}. The method was not run, " \
|
|
@@ -1528,7 +1524,7 @@ module Bitfab
|
|
|
1528
1524
|
input: kwargs.empty? ? args : [*args, kwargs],
|
|
1529
1525
|
result: fn_result,
|
|
1530
1526
|
original_output: item["output"],
|
|
1531
|
-
error: item_error
|
|
1527
|
+
error: item_error && ErrorChain.describe(item_error),
|
|
1532
1528
|
trace_error: fn_error,
|
|
1533
1529
|
replay_error:,
|
|
1534
1530
|
duration_ms: replay_duration_ms,
|
data/lib/bitfab/subtree.rb
CHANGED
|
@@ -740,7 +740,7 @@ module Bitfab
|
|
|
740
740
|
return unless entry.is_a?(OpenSpan)
|
|
741
741
|
|
|
742
742
|
exception = event.raised_exception
|
|
743
|
-
entry.error = "#{exception.class}: #{exception
|
|
743
|
+
entry.error = "#{exception.class}: #{ErrorChain.describe(exception)}"
|
|
744
744
|
end
|
|
745
745
|
|
|
746
746
|
def clear_rescued_error(event)
|
data/lib/bitfab/version.rb
CHANGED
data/lib/bitfab.rb
CHANGED
|
@@ -5,6 +5,7 @@ require "json"
|
|
|
5
5
|
require_relative "bitfab/version"
|
|
6
6
|
require_relative "bitfab/constants"
|
|
7
7
|
require_relative "bitfab/warn_once"
|
|
8
|
+
require_relative "bitfab/error_chain"
|
|
8
9
|
require_relative "bitfab/experiment_id"
|
|
9
10
|
require_relative "bitfab/payload_budget"
|
|
10
11
|
require_relative "bitfab/serialize"
|
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.75.
|
|
4
|
+
version: 0.75.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Harvest Team
|
|
@@ -183,6 +183,7 @@ files:
|
|
|
183
183
|
- lib/bitfab/db_snapshot.rb
|
|
184
184
|
- lib/bitfab/detached_trace.rb
|
|
185
185
|
- lib/bitfab/env.rb
|
|
186
|
+
- lib/bitfab/error_chain.rb
|
|
186
187
|
- lib/bitfab/experiment_id.rb
|
|
187
188
|
- lib/bitfab/experiments.rb
|
|
188
189
|
- lib/bitfab/git_command.rb
|