deployangel 0.1.38 → 0.1.40

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: 9a0944295d3cbddf9d6ff41ce21ad608b3c9615dc2910bfed6ef9745ffde6e93
4
- data.tar.gz: 7197967f339bb1af5057528e2b08b9e894379051401d059f02a91ec6cd3083ad
3
+ metadata.gz: 4e8adef48eb597e4b49551c9935dd0308a465ab3ad302de263ed47035293e15a
4
+ data.tar.gz: 98928fa20dfe57fc11b8f99b56ec2f9c33010c023de4a78f0fdffb59cdd29e65
5
5
  SHA512:
6
- metadata.gz: 9fc73a15daea7553d4a04121c001a49feef7a15195b0313b1115ab572a387cc053bf80196d6c8d79baee8c980df2833c198cdf7a2e0dcdeda18d81235dd3c208
7
- data.tar.gz: 4d5fe66f67476bd5c0e41137d6f3f38746d31da776ea295526e4becd463da21f61bf4d23a5d0ebfaac61df4c84d0d4692b67e49797d5e05a6cb63fa5e59a706f
6
+ metadata.gz: 37dd0621d760a588e12ea465902aefe678f6e9c171a15721d7ded7071af03e2e80996a8239e1f7fae4e7ca2583d647b73d403a3ef501d0dc4906499fae735c9c
7
+ data.tar.gz: d581a5108ccc9bdb4e2c13747ce556c4857a14275939aa0756d05e518da03933b76189aea11269d87fe78c6b9e7faa879c8bc0cd7f989175eeee7b1350e42ff3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.40 (2026-11-04)
4
+
5
+ - Rails apps get two checkpoints with nothing to add: `rails.mail.delivered`,
6
+ counted from Action Mailer's deliveries, and `rails.storage.stored`, from
7
+ Active Storage's uploads. A release after which mail or uploads quietly
8
+ stop fails for it, the way a checkpoint the app records would. Counts
9
+ only: never a recipient, a filename, or a key. Off with
10
+ `config.derived_checkpoints = false` or
11
+ `DEPLOYANGEL_DERIVED_CHECKPOINTS=false`.
12
+
13
+ ## 0.1.39 (2026-11-04)
14
+
15
+ - `deployangel doctor` warns when the project's AGENTS.md or CLAUDE.md has
16
+ an older copy of the block `deployangel install agents` writes, and
17
+ `deployangel status` says so on stderr at the start of a session, where
18
+ a coding agent reads it. Running `deployangel install agents` updates
19
+ it. Hand-written instructions without the block's markers aren't
20
+ checked.
21
+
3
22
  ## 0.1.38 (2026-11-04)
4
23
 
5
24
  - `deployangel verify` shows a volume drop (servers from 2026-11-04) as
@@ -258,6 +258,7 @@ module DeployAngel
258
258
  output(document, options[:format]) { Formatter.verification(document) }
259
259
  step_summary(Formatter.markdown(document))
260
260
  @stderr.puts("deployangel: timed out; verification is still in progress") if outcome.timed_out
261
+ stale_instructions_note if status
261
262
  outcome.exit_code
262
263
  end
263
264
 
@@ -406,6 +407,15 @@ module DeployAngel
406
407
  # What this checkout will report and what DeployAngel has seen of the
407
408
  # app. Warnings are advice, so only errors (a rejected token, an
408
409
  # unreachable server, no agent in the bundle) fail it.
410
+ # At the start of a session (`status`), where an agent reads it and can
411
+ # update the instructions it's following.
412
+ def stale_instructions_note
413
+ stale = Doctor.stale_instructions(@root)
414
+ return if stale.empty?
415
+
416
+ @stderr.puts("deployangel: #{Doctor.stale_instructions_note(stale)}; run `deployangel install agents` to update")
417
+ end
418
+
409
419
  def doctor
410
420
  format = parse(format: nil) { |o, opts| o.on("--format=FORMAT", %w[text json]) { |v| opts[:format] = v } }[:format]
411
421
  doctor = Doctor.new(root: @root, client: doctor_client, env: @env)
@@ -12,7 +12,7 @@ module DeployAngel
12
12
  attr_accessor :token, :endpoint, :enabled, :environments, :release_version, :revision,
13
13
  :flush_interval, :open_timeout, :read_timeout, :max_queued_payloads, :max_routes, :logger,
14
14
  :file_digests, :critical_flows, :ignored_routes, :sidekiq_cron_schedule_file, :exception_messages,
15
- :recurring_jobs
15
+ :recurring_jobs, :derived_checkpoints
16
16
 
17
17
  def initialize(env = ENV)
18
18
  @token = env["DEPLOYANGEL_TOKEN"]
@@ -43,6 +43,9 @@ module DeployAngel
43
43
  # cron line or Fugit's words ("every day at 4am"), optionally ending
44
44
  # in a time zone.
45
45
  @recurring_jobs = {}
46
+ # Checkpoints counted from Rails' own notifications, with nothing to
47
+ # add: mail delivered and files stored (DeployAngel::Rails::Checkpoints).
48
+ @derived_checkpoints = parse_boolean(env["DEPLOYANGEL_DERIVED_CHECKPOINTS"]) != false
46
49
  end
47
50
 
48
51
  # Reports only with a token, and by default in every environment but
@@ -36,10 +36,29 @@ module DeployAngel
36
36
  def checks
37
37
  @checks ||= begin
38
38
  @setup, server = server_checks
39
- [ *agent_checks, here_check, schedule_check, *server, release_check, *comparison_checks ].compact
39
+ [ *agent_checks, here_check, schedule_check, *server, release_check, *comparison_checks, instructions_check ].compact
40
40
  end
41
41
  end
42
42
 
43
+ # Files whose DeployAngel block (between the markers `deployangel install
44
+ # agents` writes) differs from this version's: agents keep following the
45
+ # older copy until it's updated. Hand-written instructions without the
46
+ # markers aren't checked.
47
+ def self.stale_instructions(root)
48
+ block = /#{Regexp.escape(CLI::AGENTS_START)}.*?#{Regexp.escape(CLI::AGENTS_END)}/m
49
+ %w[AGENTS.md CLAUDE.md].select do |name|
50
+ path = File.join(root, name)
51
+ found = File.file?(path) && File.read(path)[block]
52
+ found && found != CLI::AGENT_INSTRUCTIONS.chomp
53
+ end
54
+ rescue SystemCallError
55
+ []
56
+ end
57
+
58
+ def self.stale_instructions_note(files)
59
+ "#{files.join(" and ")} #{files.one? ? "has" : "have"} an older copy of DeployAngel's agent instructions"
60
+ end
61
+
43
62
  def failed?
44
63
  checks.any? { |check| check.status == :error }
45
64
  end
@@ -57,6 +76,14 @@ module DeployAngel
57
76
  [ Check.new(status: :error, message: "The agent isn't in the bundle", fix: "Add gem \"deployangel\" to the Gemfile and run bundle install.") ]
58
77
  end
59
78
 
79
+ def instructions_check
80
+ stale = self.class.stale_instructions(@root)
81
+ return if stale.empty?
82
+
83
+ Check.new(status: :warn, message: self.class.stale_instructions_note(stale),
84
+ fix: "Run deployangel install agents to update #{stale.one? ? "it" : "them"}.")
85
+ end
86
+
60
87
  def schedule_check
61
88
  counts = local_schedules.group_by { |schedule| schedule["source"] }.map { |source, list| "#{SOURCES.fetch(source, source)} #{list.size}" }
62
89
  later = [ ("GoodJob's cron" if read("Gemfile.lock").to_s.include?(" good_job (")), "config.recurring_jobs" ].compact
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeployAngel
4
+ module Rails
5
+ # Checkpoints the app gets without adding a line (spec §17, Silent
6
+ # failures): mail delivered and files stored, counted from Rails' own
7
+ # notifications. Work that quietly stops is what verdicts miss most, and
8
+ # these two are where it most often stops. Counts only, never a
9
+ # recipient, a filename, or a key. Off with config.derived_checkpoints =
10
+ # false, or DEPLOYANGEL_DERIVED_CHECKPOINTS=false.
11
+ module Checkpoints
12
+ MAIL_DELIVERED = "rails.mail.delivered"
13
+ FILE_STORED = "rails.storage.stored"
14
+
15
+ def self.install
16
+ return if @installed
17
+
18
+ @installed = true
19
+ ::ActiveSupport::Notifications.subscribe("deliver.action_mailer") { DeployAngel.checkpoint(MAIL_DELIVERED) }
20
+ ::ActiveSupport::Notifications.subscribe("service_upload.active_storage") { DeployAngel.checkpoint(FILE_STORED) }
21
+ end
22
+ end
23
+ end
24
+ end
@@ -9,6 +9,10 @@ module DeployAngel
9
9
  app.config.middleware.insert(0, DeployAngel::Rails::Http)
10
10
  end
11
11
 
12
+ initializer "deployangel.checkpoints" do
13
+ DeployAngel::Rails::Checkpoints.install if DeployAngel.configuration.derived_checkpoints
14
+ end
15
+
12
16
  initializer "deployangel.jobs" do
13
17
  ActiveSupport.on_load(:active_job) { DeployAngel::Rails::ActiveJob.install }
14
18
  DeployAngel::Sidekiq.install
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeployAngel
4
- VERSION = "0.1.38"
4
+ VERSION = "0.1.40"
5
5
  end
data/lib/deployangel.rb CHANGED
@@ -133,6 +133,7 @@ end
133
133
  require_relative "deployangel/rack/http"
134
134
  require_relative "deployangel/rails/http"
135
135
  require_relative "deployangel/rails/active_job"
136
+ require_relative "deployangel/rails/checkpoints"
136
137
  require_relative "deployangel/rails/error_subscriber"
137
138
  require_relative "deployangel/tasks"
138
139
  require_relative "deployangel/metadata"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deployangel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.38
4
+ version: 0.1.40
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Owens
@@ -60,6 +60,7 @@ files:
60
60
  - lib/deployangel/queue_backlog.rb
61
61
  - lib/deployangel/rack/http.rb
62
62
  - lib/deployangel/rails/active_job.rb
63
+ - lib/deployangel/rails/checkpoints.rb
63
64
  - lib/deployangel/rails/error_subscriber.rb
64
65
  - lib/deployangel/rails/http.rb
65
66
  - lib/deployangel/rails/metadata.rb