standard_health 0.6.0 → 0.7.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: 5845fe7433611028fb103afb457683b22717a8f1aa79848f425a2cd5d7c9bd7a
4
- data.tar.gz: c83d29991ba9b19b2ce234e5ef3cacabf198af323b97809927ef36608c49ba7e
3
+ metadata.gz: 4b9de071226844b0a4c1304b7b81d21ba11b963e3c4ad082aebd05a82a24fa92
4
+ data.tar.gz: dd4bab92dade63959f66db19ee8467c753e39a1f77dd12706adff721460bc14e
5
5
  SHA512:
6
- metadata.gz: eddef6b2df86d0ff116a63793b009f3b3afe81ed3f437fcd5f1fea15be0545ca2a384ea346bae3c12050ffc8f95183af4ee5414a8739ead14910b2bcd0a45ea5
7
- data.tar.gz: cb15f56c75ec0464412c16360d788b8f714d73c844a3e7bde8564295e56d2e56bdc63bb9c5987792efd65c8c3158e4e9b0edde991f0ea69e0a8dfecf93c9361f
6
+ metadata.gz: 1b6431be6025cdfb36f4059d1d295464c43d0daf927155cd53953c34cbac683ce04fbd810acc7217765be574fa4ea85eb59a8186d9326c617225705efdf878ef
7
+ data.tar.gz: 623ea6e63b5f4e11c281ad64d08e60ec10e858b960adcc6758ffa98970cc5e92cd4c0e64406d679aad2d44e387800de88f2bef521f5513899188ae81e811cba9
data/CHANGELOG.md CHANGED
@@ -7,6 +7,78 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.0] - 2026-09-24
11
+
12
+ The Phase 4 release. 0.6 deprecated nothing, so nothing is removed. It adds
13
+ the one extension point the app adoptions still needed their own controller
14
+ for. The response of `/diagnostics/env` gains a key, and a failing assertion
15
+ changes its `status`, so this ships as a minor.
16
+
17
+ ### Added
18
+
19
+ - **`Configuration#register_diagnostics_assertion(name, callable = nil, &block)`**
20
+ adds a runtime assertion to the doctor tier. `/diagnostics/env` now renders
21
+ `assertions: [...]` next to `audit`. The key is always present (`[]` when
22
+ none are registered). Each callable returns a Hash with `status:` (`:ok` /
23
+ `:warn` / `:error`) plus detail keys, and the gem sets `name:`. A raise
24
+ becomes an `:error` row with `error_class` / `error` and is reported to
25
+ `Rails.error` as handled (`context: { diagnostics_assertion: name }`,
26
+ `source: "standard_health"`). A non-Hash result or an unknown status also
27
+ becomes an `:error` row. Re-registering a name replaces it (reload-safe).
28
+ `diagnostics_assertions` / `reset_diagnostics_assertions!` are provided.
29
+ Assertions never run on `/alive`, `/ready` or the aggregate tier, and stay
30
+ behind `diagnostics_basic_auth`.
31
+ - **`StandardHealth::DiagnosticsAssertions.run` / `.failing?`**, public for
32
+ hosts that render their own diagnostics endpoint.
33
+
34
+ ### Changed
35
+
36
+ - **`/diagnostics/env` `status` also rolls up assertions.** An `:error`
37
+ assertion makes it `incomplete` (`:warn` does not). The HTTP status is still
38
+ 200. Nothing changes unless you register an assertion.
39
+ - **`StandardHealth::DiagnosticsAuthentication` is public, semver-stable
40
+ API.** Including it into a host controller (as sidekick-web does) is now a
41
+ supported extension point: the include, its single `before_action`, and the
42
+ 401 challenge / 403 fail-closed refusal. Its private method names are not
43
+ part of the contract.
44
+
45
+ ### Upgrade notes (0.6.x → 0.7.0)
46
+
47
+ Grepped `origin/main` of sidekick-web, jumpdrive-web, fundbright-web,
48
+ luminality-web and nutripod-web on 2026-09-24.
49
+
50
+ - **No required host change.** Nothing was removed.
51
+ - **Monitors or scripts that parse `/diagnostics/env`:** a new `assertions`
52
+ key appears, and `status` can become `incomplete` from an `:error`
53
+ assertion once you register one.
54
+ - **sidekick-web (optional, recommended):** move the three assertions in
55
+ `app/controllers/health_diagnostics_controller.rb` (`statement_timeout`,
56
+ `rate_limit_store`, `attestation_roots`) into
57
+ `register_diagnostics_assertion` calls in
58
+ `config/initializers/standard_health.rb`. Then delete the controller and its
59
+ `config/routes.rb` route that shadows the engine's `/health/diagnostics/env`.
60
+ Keep each assertion's Hash shape. Drop the per-assertion
61
+ `rescue => e; { status: :error, error: e.message }`, which the gem now
62
+ provides (it also adds `error_class`). The response gains `status`, which
63
+ the host controller never rendered.
64
+ - Other apps have no diagnostics controller of their own. Nothing to do.
65
+
66
+ ### Documentation
67
+
68
+ - Aggregate-tier check **timeouts** are deliberately not sent to `Rails.error` (true since 0.6.1). A timeout is the gem's own budget firing on a slow dependency, already emitted as `standard_health.check.timed_out`, and the pre-0.6 host controllers had no per-check timeouts. A code comment and a spec now pin this.
69
+
70
+ ## [0.6.1] - 2026-09-24
71
+
72
+ ### Fixed
73
+
74
+ - **A check that raises on the aggregate tier is reported to `Rails.error` again.** Before 0.6.0 every host's aggregate controller rescued a raising check and called `Rails.error.report(e, handled: true, ...)`. When the engine took over the tier, the exception was only logged by the Logger notifier, so a buggy aggregate check stopped reaching Sentry (noted in the jumpdrive-web and sidekick-web adoptions). The Aggregator now reports it as handled with context `{ health_check: <name>, tier: "aggregate" }`. Severity follows the readiness Sentry notifier: `:error` for a critical check, `:warning` otherwise. The readiness tier is unchanged. Its failures still reach Sentry through the transition-gated `ready.evaluated` notifier, which keeps a ~6/min probe from flooding the tracker. Checks that return a `:fail` row (every built-in check) are not reported. The report is rescued, so a broken error subscriber cannot 500 the tier.
75
+
76
+ ### Documentation
77
+
78
+ - The aggregate tier re-runs the `register_check` readiness checks by default (`aggregate_readiness_checks = true`), so a failing critical check (database, Solid Queue) makes `/health` answer **503 `unavailable`**, not only `/ready`.
79
+ - The aggregate body reports `"unavailable"`, never StandardCircuit's `"critical"`. Monitors matching on `"critical"` need updating (as noted in 0.6.0's migration notes).
80
+ - `StandardHealth::DiagnosticsAuthentication` is the request-time gate behind `diagnostics_basic_auth`. The README now notes that a host can include it into its own diagnostics controller, as sidekick-web does. It is not yet a documented, semver-stable extension point.
81
+
10
82
  ## [0.6.0] - 2026-09-24
11
83
 
12
84
  DX release: absorbs the code every consumer app copy-pasted around the engine.
data/README.md CHANGED
@@ -109,14 +109,31 @@ end
109
109
  | `degraded` | 200 | a non-critical check failed or was skipped, or the circuit roll-up is `:degraded` |
110
110
  | `ok` | 200 | otherwise |
111
111
 
112
+ **The aggregate tier re-runs your readiness checks by default.** With
113
+ `aggregate_readiness_checks` on (the default), every `register_check`
114
+ registration runs here as well as on `/ready`. A failing critical check, such
115
+ as `:database` or `:solid_queue` from `register_default_checks`, therefore turns
116
+ `/health` into a **503 `unavailable`**, not just `/ready`. Point an uptime
117
+ monitor at `/health` and a database outage pages as a 503. If your aggregate
118
+ tier should only report soft signals (sidekick), set
119
+ `aggregate_readiness_checks = false`.
120
+
112
121
  StandardCircuit's own word `critical` is translated to `unavailable`, so both
113
- tiers speak one vocabulary. `circuits` is omitted when StandardCircuit isn't
122
+ tiers speak one vocabulary. The aggregate body never says `"critical"`. `circuits` is omitted when StandardCircuit isn't
114
123
  loaded (or `aggregate_circuits = false`); if `health_report` raises (circuit
115
124
  store down) the tier degrades and reports `circuits_error: { error_class,
116
125
  error_code }` instead of 500ing. Check rows are redacted exactly like `/ready`,
117
126
  with the same `detail_token` break-glass. `register_aggregate_check` checks
118
127
  run **only** here — never on `/ready` — and default to `critical: false`.
119
128
 
129
+ A check that **raises** on this tier, rather than returning a `:fail` row, is
130
+ also reported to `Rails.error` as handled (since 0.6.1), with context
131
+ `{ health_check:, tier: "aggregate" }` and severity `:error` for a critical
132
+ check or `:warning` otherwise. That matches what the pre-0.6 host controllers
133
+ did. Per-check timeouts are not reported; they surface as
134
+ `standard_health.check.timed_out`. `/ready` does not do this: its failures reach Sentry through the
135
+ transition-gated `ready.evaluated` notifier.
136
+
120
137
  With `aggregate_endpoint` off (the default) the engine's root route carries a
121
138
  per-request constraint that never matches, so a bare `/health` cascades to your
122
139
  own route exactly as before. Once on, the engine answers `/health` regardless
@@ -402,11 +419,13 @@ Since 0.4.1 the response carries a `status` alongside the audit, so a caller
402
419
  can gate on one field instead of re-implementing the roll-up:
403
420
 
404
421
  ```json
405
- { "mode": "production", "status": "incomplete", "audit": [ ... ] }
422
+ { "mode": "production", "status": "incomplete", "audit": [ ... ], "assertions": [ ... ] }
406
423
  ```
407
424
 
408
425
  `incomplete` means at least one row is a **violation** — `missing`,
409
- `forbidden`, or `mismatch`. `should_set` is advisory and never affects it.
426
+ `forbidden`, or `mismatch` — or (since 0.7.0) a registered diagnostics
427
+ assertion reported `error`. `should_set` and an assertion's `warn` are
428
+ advisory and never affect it.
410
429
 
411
430
  The level is deliberately not consulted for `mismatch`: a `recommended` var
412
431
  declared with an `expected_value:` that does not hold is a failed assertion,
@@ -417,6 +436,56 @@ not advice.
417
436
  free — which is why they joined this roll-up rather than getting a verdict of
418
437
  their own.
419
438
 
439
+ ### Runtime assertions: `register_diagnostics_assertion` (0.7.0)
440
+
441
+ Env presence can't prove that a setting took effect. For example, the live
442
+ `statement_timeout` may still be `0`, a rate limiter may have fallen back to
443
+ Solid Cache, or a pinned certificate may be about to expire. Register those
444
+ checks as assertions and the engine renders them on `/diagnostics/env` under
445
+ `assertions:`, behind the same `diagnostics_basic_auth` gate:
446
+
447
+ ```ruby
448
+ StandardHealth.configure do |c|
449
+ c.register_diagnostics_assertion(:statement_timeout) do
450
+ value = ActiveRecord::Base.connection.select_value("SHOW statement_timeout").to_s
451
+ { status: value.strip == "0" ? :warn : :ok, value: value, expected: "non-zero" }
452
+ end
453
+
454
+ # Any callable works; reference app constants inside it, not at boot.
455
+ c.register_diagnostics_assertion(:rate_limit_store, -> { RateLimitAssertion.call })
456
+ end
457
+ ```
458
+
459
+ ```json
460
+ { "mode": "production", "status": "ok", "audit": [ ... ],
461
+ "assertions": [ { "name": "statement_timeout", "status": "ok", "value": "15s", "expected": "non-zero" } ] }
462
+ ```
463
+
464
+ - The callable takes no arguments and returns a Hash with `status:` set to
465
+ `:ok`, `:warn` or `:error`. Other keys are rendered as-is. The gem sets
466
+ `name:`, and the callable can't override it.
467
+ - An assertion that raises becomes `{ status: "error", error_class:, error: }`
468
+ and is reported to `Rails.error` as handled. The tier is authenticated, so
469
+ the message is shown. A non-Hash result or an unknown status also becomes an
470
+ `:error` row. One broken assertion never takes the endpoint down.
471
+ - An `:error` row makes the top-level `status` `incomplete`. `:warn` does not.
472
+ The endpoint still returns 200.
473
+ - Assertions run per request, and only on this tier, never on `/alive`,
474
+ `/ready` or the aggregate. They have no timeout, so keep them cheap.
475
+ - Re-registering a name replaces it, so it is safe inside `to_prepare`.
476
+ `reset_diagnostics_assertions!` clears them in specs.
477
+ - A host that keeps its own diagnostics controller can render
478
+ `StandardHealth::DiagnosticsAssertions.run` itself.
479
+
480
+ **Replace your host code with it.** sidekick-web's
481
+ `app/controllers/health_diagnostics_controller.rb` exists to add three
482
+ assertions (`statement_timeout`, `rate_limit_store`, `attestation_roots`) to
483
+ the env audit. Move each into a `register_diagnostics_assertion`, delete the
484
+ controller, and delete its route
485
+ (`get "/health/diagnostics/env", to: "health_diagnostics#env"` ahead of the
486
+ engine mount). Its `rescue => e; { status: :error, error: e.message }`
487
+ wrappers go too, because the gem does that now.
488
+
420
489
  ### Surfacing the audit on a health tier
421
490
 
422
491
  `/diagnostics/env` is authed and polled by nobody, so config drift declared in
@@ -590,6 +659,17 @@ c.diagnostics_basic_auth = {
590
659
  nothing.
591
660
  - Off by default; independent of `diagnostics_parent_controller` (if you set
592
661
  both, the parent's callbacks run first).
662
+ - The request-time gate is the `StandardHealth::DiagnosticsAuthentication`
663
+ concern, which the engine includes into its own `DiagnosticsController`.
664
+ **Since 0.7.0 it is public, semver-stable API:** `include
665
+ StandardHealth::DiagnosticsAuthentication` into any `ActionController::API`
666
+ or `::Base` controller to put a host endpoint behind the same fail-closed
667
+ gate. The contract is the include, the one `before_action` it installs, and
668
+ the 401 challenge / 403 refusal behaviour. Its private method names are not
669
+ part of it. It is a no-op until `diagnostics_basic_auth` is set. If your
670
+ controller only exists to add runtime assertions, use
671
+ [`register_diagnostics_assertion`](#runtime-assertions-register_diagnostics_assertion-070)
672
+ instead and delete the controller.
593
673
 
594
674
  **Replace your host code with it.** Delete
595
675
  `app/controllers/standard_health_host_controller.rb` and the
@@ -7,6 +7,15 @@ module StandardHealth
7
7
  # `DiagnosticsController` only — never `HealthController` — so /alive and
8
8
  # /ready stay anonymous for orchestrator probes.
9
9
  #
10
+ # PUBLIC API (since 0.7.0, semver-stable): a host that serves its own
11
+ # diagnostics endpoint can `include StandardHealth::DiagnosticsAuthentication`
12
+ # into any ActionController::API or ::Base controller to put it behind the
13
+ # same fail-closed gate. The contract is the include, the `before_action`
14
+ # it installs, and the 401 challenge / 403 refusal behaviour below; the
15
+ # private method names are not part of it. Prefer
16
+ # `register_diagnostics_assertion` when all the host controller adds is
17
+ # assertions.
18
+ #
10
19
  # A NO-OP when `diagnostics_basic_auth` is unset, so hosts that gate
11
20
  # diagnostics through `diagnostics_parent_controller` see no change. Both
12
21
  # can be used together; the parent's callbacks run first.
@@ -27,11 +27,13 @@ module StandardHealth
27
27
  root = defined?(Rails) ? Rails.root : nil
28
28
 
29
29
  audit = spec ? spec.audit(ENV.to_h, mode: mode, root: root) : []
30
+ assertions = DiagnosticsAssertions.run
30
31
 
31
32
  render json: {
32
33
  mode: mode,
33
- status: audit_status(audit),
34
+ status: audit_status(audit, assertions),
34
35
  audit: audit,
36
+ assertions: assertions,
35
37
  generated_at: Time.now.utc.iso8601
36
38
  }
37
39
  end
@@ -53,13 +55,16 @@ module StandardHealth
53
55
  # advice. The advisory status stays `:should_set`, which is not a
54
56
  # violation.
55
57
  #
58
+ # A registered diagnostics assertion (`register_diagnostics_assertion`)
59
+ # that reports `:error` also makes it `:incomplete`; `:warn` does not.
60
+ #
56
61
  # The endpoint still returns 200 either way, so nothing that asserts on
57
62
  # the status code breaks.
58
- def audit_status(audit)
63
+ def audit_status(audit, assertions = [])
59
64
  violated = Array(audit).any? do |row|
60
65
  EnvSpec::VIOLATION_STATUSES.include?(row[:status])
61
66
  end
62
- violated ? :incomplete : :ok
67
+ (violated || DiagnosticsAssertions.failing?(assertions)) ? :incomplete : :ok
63
68
  end
64
69
  end
65
70
  end
@@ -111,6 +111,12 @@ module StandardHealth
111
111
  emit_check(row, tier)
112
112
  row
113
113
  rescue CheckTimeout
114
+ # Deliberately NOT sent to Rails.error (unlike the StandardError branch
115
+ # below). A timeout is our own budget firing on a slow dependency, not a
116
+ # bug in the check: it is already visible as `check.timed_out` and as a
117
+ # failing row, and reporting it per poll would page on latency. The
118
+ # pre-0.6 host aggregate controllers had no per-check timeouts, so
119
+ # nothing reported there is lost.
114
120
  emit("standard_health.check.timed_out",
115
121
  name: reg.name, critical: reg.critical, timeout_s: timeout, **tier_attrs(tier))
116
122
  row = {
@@ -131,10 +137,38 @@ module StandardHealth
131
137
  error_class: e.class.name
132
138
  }
133
139
  emit_check(row, tier)
140
+ report_raised_check(e, reg, tier)
134
141
  row
135
142
  end
136
143
  private_class_method :safe_run
137
144
 
145
+ # A check that RAISES (rather than returning a :fail row) on a
146
+ # non-readiness tier goes to `Rails.error` as handled. Before 0.6.0 every
147
+ # host's aggregate controller did exactly this; moving the tier into the
148
+ # engine left the exception only in the Logger notifier's line, so a buggy
149
+ # aggregate check stopped reaching the error tracker.
150
+ #
151
+ # Readiness is deliberately excluded: its failures already reach Sentry
152
+ # through the transition-gated `ready.evaluated` notifier, and reporting
153
+ # every raise from a ~6/min/instance probe would be exactly the noise that
154
+ # notifier exists to prevent. Severity mirrors that notifier: a critical
155
+ # check is an :error (it makes the tier :unavailable), otherwise :warning.
156
+ def self.report_raised_check(error, reg, tier)
157
+ return if tier == :ready
158
+ return unless defined?(::Rails) && ::Rails.respond_to?(:error) && ::Rails.error
159
+
160
+ ::Rails.error.report(
161
+ error,
162
+ handled: true,
163
+ severity: reg.critical ? :error : :warning,
164
+ context: { health_check: reg.name.to_s, tier: tier.to_s }
165
+ )
166
+ rescue StandardError
167
+ # Never-raise: a broken error subscriber must not 500 the tier.
168
+ nil
169
+ end
170
+ private_class_method :report_raised_check
171
+
138
172
  def self.budget_exhausted?(budget, started)
139
173
  return false unless budget
140
174
 
@@ -220,6 +220,7 @@ module StandardHealth
220
220
  @env_spec = nil
221
221
  @checks = []
222
222
  @aggregate_checks = []
223
+ @diagnostics_assertions = []
223
224
  @aggregate_endpoint = false
224
225
  @aggregate_readiness_checks = true
225
226
  @aggregate_circuits = true
@@ -368,6 +369,51 @@ module StandardHealth
368
369
  registration
369
370
  end
370
371
 
372
+ DiagnosticsAssertion = Struct.new(:name, :callable)
373
+
374
+ # Adds a runtime assertion to the doctor tier (`GET /diagnostics/env`),
375
+ # rendered under `assertions:` next to the env audit. For facts env
376
+ # presence can't prove: the live `statement_timeout`, the cache store a
377
+ # rate limiter actually resolved to, a pinned root's expiry. Replaces a
378
+ # host diagnostics controller that exists only to add these.
379
+ #
380
+ # c.register_diagnostics_assertion(:statement_timeout) do
381
+ # value = ActiveRecord::Base.connection.select_value("SHOW statement_timeout").to_s
382
+ # { status: value == "0" ? :warn : :ok, value: value, expected: "non-zero" }
383
+ # end
384
+ #
385
+ # The callable takes no arguments and returns a Hash with `status:`
386
+ # (`:ok`, `:warn` or `:error`) plus any detail keys. `name:` is set by
387
+ # the gem. It runs per request, on the authenticated tier only, never on
388
+ # /alive, /ready or the aggregate. A raise, a non-Hash, or an unknown
389
+ # status becomes an `:error` row (a raise is also reported to
390
+ # `Rails.error` as handled). An `:error` row makes the endpoint's
391
+ # `status` `incomplete`; `:warn` does not.
392
+ #
393
+ # Re-registering a name replaces it, so `configure` can safely re-run
394
+ # from `to_prepare` on reload.
395
+ def register_diagnostics_assertion(name, callable = nil, &block)
396
+ callable ||= block
397
+ unless callable.respond_to?(:call)
398
+ raise ArgumentError, "register_diagnostics_assertion(#{name.inspect}) needs a callable or a block"
399
+ end
400
+
401
+ assertion = DiagnosticsAssertion.new(name.to_sym, callable)
402
+ @diagnostics_assertions.reject! { |existing| existing.name == assertion.name }
403
+ @diagnostics_assertions << assertion
404
+ assertion
405
+ end
406
+
407
+ # @return [Array<DiagnosticsAssertion>] in registration order
408
+ def diagnostics_assertions
409
+ @diagnostics_assertions.dup
410
+ end
411
+
412
+ # Drop registered diagnostics assertions. Test hygiene.
413
+ def reset_diagnostics_assertions!
414
+ @diagnostics_assertions = []
415
+ end
416
+
371
417
  # @return [Array<Registration>] aggregate-only checks
372
418
  def aggregate_checks
373
419
  @aggregate_checks.dup
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StandardHealth
4
+ # Runs the host's `register_diagnostics_assertion` callables for the doctor
5
+ # tier (`/diagnostics/env`, behind `diagnostics_basic_auth`).
6
+ #
7
+ # An assertion is a runtime fact that env presence cannot express: the live
8
+ # `statement_timeout`, which cache store a limiter actually resolved to, how
9
+ # close a pinned certificate is to expiry. Each callable returns a Hash with
10
+ # at least `status:` (`:ok`, `:warn` or `:error`); any other keys (`value:`,
11
+ # `expected:`, ...) are rendered as-is. The gem sets `name:`.
12
+ #
13
+ # Public: a host with its own diagnostics controller can render
14
+ # `StandardHealth::DiagnosticsAssertions.run` itself.
15
+ module DiagnosticsAssertions
16
+ STATUSES = %i[ok warn error].freeze
17
+
18
+ module_function
19
+
20
+ # @return [Array<Hash>] one row per registered assertion, in registration
21
+ # order. Never raises: an assertion that raises, returns a non-Hash, or
22
+ # returns an unknown status becomes an `:error` row.
23
+ def run(config = StandardHealth.config)
24
+ config.diagnostics_assertions.map { |assertion| evaluate(assertion) }
25
+ end
26
+
27
+ # True when any row is `:error`. `:warn` is advisory.
28
+ def failing?(rows)
29
+ Array(rows).any? { |row| row[:status] == :error }
30
+ end
31
+
32
+ def evaluate(assertion)
33
+ result = assertion.callable.call
34
+ return invalid(assertion.name, "returned #{result.class}, expected a Hash") unless result.is_a?(Hash)
35
+
36
+ row = result.to_h.transform_keys(&:to_sym)
37
+ status = row[:status].respond_to?(:to_sym) ? row[:status].to_sym : nil
38
+ return invalid(assertion.name, "returned status #{row[:status].inspect}, expected one of #{STATUSES.inspect}") unless STATUSES.include?(status)
39
+
40
+ { name: assertion.name }.merge(row.except(:name)).merge(status: status)
41
+ rescue StandardError => e
42
+ report(e, assertion.name)
43
+ # The doctor tier is authenticated, so the message is shown (the /ready
44
+ # redaction rationale does not apply); the class is kept separately
45
+ # for grouping.
46
+ { name: assertion.name, status: :error, error_class: e.class.name, error: e.message }
47
+ end
48
+
49
+ def invalid(name, detail)
50
+ { name: name, status: :error, error: "diagnostics assertion #{name} #{detail}" }
51
+ end
52
+
53
+ def report(error, name)
54
+ return unless defined?(::Rails) && ::Rails.respond_to?(:error) && ::Rails.error
55
+
56
+ ::Rails.error.report(error, handled: true, severity: :warning,
57
+ context: { diagnostics_assertion: name }, source: "standard_health")
58
+ rescue StandardError
59
+ nil
60
+ end
61
+ end
62
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StandardHealth
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
@@ -22,6 +22,7 @@ require "standard_health/subscribers"
22
22
  require "standard_health/redactor"
23
23
  require "standard_health/aggregator"
24
24
  require "standard_health/aggregate_report"
25
+ require "standard_health/diagnostics_assertions"
25
26
 
26
27
  module StandardHealth
27
28
  class << self
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: standard_health
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim
@@ -54,6 +54,7 @@ files:
54
54
  - lib/standard_health/checks/solid_cache.rb
55
55
  - lib/standard_health/checks/solid_queue.rb
56
56
  - lib/standard_health/configuration.rb
57
+ - lib/standard_health/diagnostics_assertions.rb
57
58
  - lib/standard_health/diagnostics_basic_auth.rb
58
59
  - lib/standard_health/engine.rb
59
60
  - lib/standard_health/env_spec.rb