deployangel 0.1.36 → 0.1.38
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/CHANGELOG.md +20 -0
- data/README.md +3 -2
- data/lib/deployangel/cli/formatter.rb +28 -5
- data/lib/deployangel/cli.rb +4 -0
- data/lib/deployangel/core/fingerprint.rb +7 -3
- data/lib/deployangel/mcp.rb +6 -1
- data/lib/deployangel/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9a0944295d3cbddf9d6ff41ce21ad608b3c9615dc2910bfed6ef9745ffde6e93
|
|
4
|
+
data.tar.gz: 7197967f339bb1af5057528e2b08b9e894379051401d059f02a91ec6cd3083ad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9fc73a15daea7553d4a04121c001a49feef7a15195b0313b1115ab572a387cc053bf80196d6c8d79baee8c980df2833c198cdf7a2e0dcdeda18d81235dd3c208
|
|
7
|
+
data.tar.gz: 4d5fe66f67476bd5c0e41137d6f3f38746d31da776ea295526e4becd463da21f61bf4d23a5d0ebfaac61df4c84d0d4692b67e49797d5e05a6cb63fa5e59a706f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.38 (2026-11-04)
|
|
4
|
+
|
|
5
|
+
- `deployangel verify` shows a volume drop (servers from 2026-11-04) as
|
|
6
|
+
what ran against what was expected (`0 requests, about 27 expected`),
|
|
7
|
+
and lists findings a person accepted as an intended change, with who
|
|
8
|
+
and why. A failing finding that could be intended gets the link where a
|
|
9
|
+
person can accept it; the CLI and MCP server can't, so an agent never
|
|
10
|
+
accepts its own failure.
|
|
11
|
+
- The MCP server's instructions, and the block `deployangel install
|
|
12
|
+
agents` writes, tell agents to hand that link to the user with their
|
|
13
|
+
evidence and never report the release as a success.
|
|
14
|
+
|
|
15
|
+
## 0.1.37 (2026-11-03)
|
|
16
|
+
|
|
17
|
+
- An exception raised entirely in gems, with no frame from the app's
|
|
18
|
+
code, now sends its first library frames as its stack trace
|
|
19
|
+
(`net-smtp/lib/net/smtp.rb#start`), normalized like the top frame, with
|
|
20
|
+
no absolute paths or versions. Before, it sent none, so an error such as
|
|
21
|
+
a mailer job's `Net::ReadTimeout` showed no trace.
|
|
22
|
+
|
|
3
23
|
## 0.1.36 (2026-11-01)
|
|
4
24
|
|
|
5
25
|
- `deployangel exercise --param id=42` fills path parameters (`:id`,
|
data/README.md
CHANGED
|
@@ -149,7 +149,8 @@ the dashboard says so.
|
|
|
149
149
|
- Release identity, runtime versions, and a per-process instance ID.
|
|
150
150
|
|
|
151
151
|
- Exceptions: a stable fingerprint, the exception class, the first line of the
|
|
152
|
-
message, and application frames only
|
|
152
|
+
message, and application frames only, or the first library frames (gem
|
|
153
|
+
paths without versions) when none is the app's. In the message, numbers, IDs, emails,
|
|
153
154
|
UUIDs, long hex strings, and quoted values are replaced with placeholders, and
|
|
154
155
|
so are the request's host and, with ros-apartment, the tenants the request or
|
|
155
156
|
job switched to. Other words are kept: `Payment failed for Jane Doe` is sent
|
|
@@ -238,7 +239,7 @@ end
|
|
|
238
239
|
### Exception messages
|
|
239
240
|
|
|
240
241
|
To send exceptions without any message, only their class, fingerprint, and
|
|
241
|
-
|
|
242
|
+
stack frames:
|
|
242
243
|
|
|
243
244
|
```ruby
|
|
244
245
|
DeployAngel.configure do |config|
|
|
@@ -10,7 +10,7 @@ module DeployAngel
|
|
|
10
10
|
"job_failure_rate" => "Job failure rate", "job_duration" => "Job duration p95", "queue_latency" => "Queue latency p95",
|
|
11
11
|
"new_fingerprint" => "New exception", "fingerprint_amplification" => "Exception amplification",
|
|
12
12
|
"missing_recurring_job" => "Recurring job", "first_use_failure" => "First-use failure", "external_check" => "External check",
|
|
13
|
-
"checkpoint_rate" => "Checkpoint"
|
|
13
|
+
"checkpoint_rate" => "Checkpoint", "volume_drop" => "Volume"
|
|
14
14
|
}.freeze
|
|
15
15
|
DURATIONS = %w[p95_latency job_duration queue_latency].freeze
|
|
16
16
|
# Exercise plan statuses with something worth running now.
|
|
@@ -18,6 +18,9 @@ module DeployAngel
|
|
|
18
18
|
REASONS = { "normally_active" => "normally active", "changed_in_release" => "changed in this release",
|
|
19
19
|
"rarely_used" => "rarely used", "critical_flow" => "critical flow" }.freeze
|
|
20
20
|
COUNTS = %w[new_fingerprint checkpoint_rate].freeze
|
|
21
|
+
# Findings worth listing: problems, and changes a person accepted as
|
|
22
|
+
# intended (servers from 2026-11-04 send "accepted").
|
|
23
|
+
LISTED = %w[failing warning accepted].freeze
|
|
21
24
|
|
|
22
25
|
module_function
|
|
23
26
|
|
|
@@ -26,7 +29,10 @@ module DeployAngel
|
|
|
26
29
|
problems = problems(document)
|
|
27
30
|
if problems.any?
|
|
28
31
|
lines << "Findings:"
|
|
29
|
-
problems.each
|
|
32
|
+
problems.each do |f|
|
|
33
|
+
lines << " #{f["status"].ljust(8)} #{finding_line(f)}"
|
|
34
|
+
acceptance_lines(f).each { |line| lines << " #{line}" }
|
|
35
|
+
end
|
|
30
36
|
end
|
|
31
37
|
if (gathering = gathering_line(document))
|
|
32
38
|
lines << "#{gathering} (--format=json for details)"
|
|
@@ -44,7 +50,7 @@ module DeployAngel
|
|
|
44
50
|
problems = problems(document)
|
|
45
51
|
if problems.any?
|
|
46
52
|
lines.push("", "| Status | Finding |", "| --- | --- |")
|
|
47
|
-
problems.each { |f| lines << "| #{f["status"]} | #{cell(finding_line(f))} |" }
|
|
53
|
+
problems.each { |f| lines << "| #{f["status"]} | #{cell([ finding_line(f), *acceptance_lines(f) ].join(". "))} |" }
|
|
48
54
|
end
|
|
49
55
|
if (gathering = gathering_line(document))
|
|
50
56
|
lines.push("", gathering)
|
|
@@ -85,7 +91,19 @@ module DeployAngel
|
|
|
85
91
|
end
|
|
86
92
|
|
|
87
93
|
def problems(document)
|
|
88
|
-
Array(document["findings"]).select { |f|
|
|
94
|
+
Array(document["findings"]).select { |f| LISTED.include?(f["status"]) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Who accepted a finding as an intended change, or where a person can.
|
|
98
|
+
# Only a person can, on the release page: the API can't.
|
|
99
|
+
def acceptance_lines(finding)
|
|
100
|
+
if (accepted = finding["accepted_as_intended"])
|
|
101
|
+
[ "Accepted as intended by #{accepted["by"]}: #{accepted["note"]}" ]
|
|
102
|
+
elsif finding["accept_url"]
|
|
103
|
+
[ "If this change was intended, a person can accept it: #{finding["accept_url"]}" ]
|
|
104
|
+
else
|
|
105
|
+
[]
|
|
106
|
+
end
|
|
89
107
|
end
|
|
90
108
|
|
|
91
109
|
def gathering_line(document)
|
|
@@ -216,7 +234,12 @@ module DeployAngel
|
|
|
216
234
|
|
|
217
235
|
def finding_line(finding)
|
|
218
236
|
label = "#{LABELS.fetch(finding["signal"], finding["signal"])} on #{finding["scope"]}"
|
|
219
|
-
if
|
|
237
|
+
if finding["signal"] == "volume_drop"
|
|
238
|
+
# Its baseline value is what the release before predicts for this
|
|
239
|
+
# much traffic.
|
|
240
|
+
unit = finding["scope"].to_s.start_with?("route:") ? "requests" : "runs"
|
|
241
|
+
"#{label}: #{finding["observed_value"].to_i} #{unit}, about #{finding["baseline_value"].to_f.round} expected"
|
|
242
|
+
elsif COUNTS.include?(finding["signal"])
|
|
220
243
|
"#{label}: #{finding["observed_value"].to_i} occurrences"
|
|
221
244
|
elsif finding["signal"] == "missing_recurring_job"
|
|
222
245
|
# Its value is the job's interval in seconds, not a rate.
|
data/lib/deployangel/cli.rb
CHANGED
|
@@ -112,6 +112,10 @@ module DeployAngel
|
|
|
112
112
|
investigate the likely cause, and propose a fix. `next_steps` names the last
|
|
113
113
|
cleared release a rollback would go to. Do not roll back or change
|
|
114
114
|
production without explicit approval.
|
|
115
|
+
- A failed release whose failing finding is a volume or checkpoint drop you
|
|
116
|
+
made on purpose (a route or job you removed): you can't accept it. Tell the
|
|
117
|
+
user why you think it was intended, with your evidence, and give them the
|
|
118
|
+
finding's `accept_url` so a person decides. It stays failed until then.
|
|
115
119
|
- Exit 3: still verifying; run the command again.
|
|
116
120
|
- Not cleared yet: call `get_exercise_plan` (or `bundle exec deployangel plan`).
|
|
117
121
|
If its status is "exercisable" or "waiting_for_activity", run
|
|
@@ -46,10 +46,14 @@ module DeployAngel
|
|
|
46
46
|
"#{normalize_path(path, root)}##{label}"
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
# Application frames only, relative to the root.
|
|
49
|
+
# Application frames only, relative to the root. An exception raised
|
|
50
|
+
# entirely in gems (a mailer job's SMTP timeout) has none, so it gets its
|
|
51
|
+
# first frames instead, normalized as in top_frame: they're the only way
|
|
52
|
+
# to tell which gem's path failed.
|
|
50
53
|
def backtrace(exception, root:)
|
|
51
|
-
locations(exception)
|
|
52
|
-
|
|
54
|
+
frames = locations(exception)
|
|
55
|
+
app = frames.select { |path, _| app_path?(path, root) }
|
|
56
|
+
(app.empty? ? frames : app).first(MAX_BACKTRACE).map { |path, label| "#{normalize_path(path, root)}##{label}" }
|
|
53
57
|
end
|
|
54
58
|
|
|
55
59
|
# Only quoted values, numbers, emails, UUIDs, and long hex are replaced;
|
data/lib/deployangel/mcp.rb
CHANGED
|
@@ -31,6 +31,11 @@ module DeployAngel
|
|
|
31
31
|
Only act on status "exercisable" or "waiting_for_activity"; for "warm_up" or
|
|
32
32
|
"no_baseline" nothing you run can clear it. Exercise routes marked mutating only with a test account
|
|
33
33
|
or after asking. Report what you ran with the plan's report_with command (deployangel check).
|
|
34
|
+
A failing volume_drop or checkpoint_rate finding with an accept_url may be an intended change, such as
|
|
35
|
+
a route or job you removed on purpose. You cannot accept it, and must not try: tell the user why you
|
|
36
|
+
think it was intended, with your evidence, and give them the accept_url so a person decides. Until
|
|
37
|
+
someone accepts it, the release is failed: never report it as success. A finding with status
|
|
38
|
+
accepted was accepted as intended by the person in accepted_as_intended.
|
|
34
39
|
TEXT
|
|
35
40
|
|
|
36
41
|
TARGET_PROPERTIES = {
|
|
@@ -117,7 +122,7 @@ module DeployAngel
|
|
|
117
122
|
TARGET_PROPERTIES),
|
|
118
123
|
tool("list_deployments", "Recent deployments with their verification state and verdict.",
|
|
119
124
|
{ "limit" => { "type" => "integer", "minimum" => 1, "maximum" => 50, "default" => 10 } }),
|
|
120
|
-
tool("get_exception", "Sanitized details and
|
|
125
|
+
tool("get_exception", "Sanitized details and a representative stack trace (application frames, or library frames when there are none) for an exception fingerprint.",
|
|
121
126
|
{ "fingerprint" => { "type" => "string" } }, required: %w[fingerprint]),
|
|
122
127
|
tool("list_late_regressions", "Failures found after a release was cleared, on paths the clearance did not cover.",
|
|
123
128
|
{ "since" => { "type" => "string", "description" => "ISO 8601 time" },
|
data/lib/deployangel/version.rb
CHANGED