standard_health 0.7.1 → 0.8.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: 162374f071e6634d43876ef75f8fa6f50b1ec7bfd6a8d01bd94a91c854151d09
4
- data.tar.gz: 58c40844359768a3d905c75575599dc1bf61dab15ebabc372c3e5e56cd4e169d
3
+ metadata.gz: 3cf58624de0179c4903a4159e227baa82079b2a78e97679122a429b186783918
4
+ data.tar.gz: 33963ed5e02ec9794a3133e4fbf3444cb443bc821045ec8bde6ecfb4b575f0a0
5
5
  SHA512:
6
- metadata.gz: a93890d618342991d02699f9a68d0cf432596f4fba5eff7659d4d4c8675d22e5e0bac3d996abed41e02ddd7f53b84c771f21e093a6c6e22b39404ec9ccc7498c
7
- data.tar.gz: 13b347704251b47c6a877ce5fb559686d9fee639610109300da1fd989627010a3823f678ab420330a26ac851548647da3ab12405081cd13a2d9024c90d4bb5dc
6
+ metadata.gz: 846b8334841aeb56c429f8a767f76c1e3e22e849da7f5de261370895cb3d7e735e2ee1fe3a72508637474f0338b34c05848bdda32e359bee783857ca3dd85e79
7
+ data.tar.gz: b6fefd789b745ec5a20819c46b89d610b226283b09024db1426ba5aacebee6838941f9d9c423b152bc55df2ab09bd341906cc5b15de6e2a7c0c59a5891d291f6
data/CHANGELOG.md CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.8.0] - 2026-10-22
11
+
12
+ ### Changed
13
+
14
+ - **Re-registering a check replaces it.** `register_check` and
15
+ `register_aggregate_check` replace an existing registration of the same name
16
+ in place (last writer wins) instead of appending a second one, matching
17
+ `register_diagnostics_assertion`. A `configure` block that runs twice, e.g.
18
+ from `to_prepare` on reload, no longer runs every check twice.
19
+ - **`env_spec` aliases are checked at boot.** Assigning `c.env_spec = …` calls
20
+ the new `EnvSpec#validate!`, which raises `EnvSpec::UnknownModeAlias` naming
21
+ every `in:` alias that has no `mode_alias`. Before, an undeclared alias raised
22
+ only on the first audit, which was a production `/diagnostics/env` request
23
+ (it 500'd one app's doctor tier once). All five estate apps declare every
24
+ alias they use, so none of them is affected.
25
+
10
26
  ## [0.7.1] - 2026-09-25
11
27
 
12
28
  ### Fixed
data/README.md CHANGED
@@ -362,7 +362,7 @@ end
362
362
 
363
363
  - `nil` (omitted) — entry always applies
364
364
  - `Array<String>` — literal mode list (existing behaviour)
365
- - `Symbol` — resolved against `mode_alias` at audit time. An undeclared Symbol raises `StandardHealth::EnvSpec::UnknownModeAlias`.
365
+ - `Symbol` — resolved against `mode_alias`. Assigning the spec (`c.env_spec = …`) checks every alias, so an undeclared Symbol raises `StandardHealth::EnvSpec::UnknownModeAlias` at boot, naming each unknown alias, rather than on the first `/diagnostics/env` request.
366
366
 
367
367
  ### `description:` and `consumed_by:`
368
368
 
@@ -522,6 +522,10 @@ end
522
522
 
523
523
  `with_timing` captures `latency_ms` on success and converts any `StandardError` into `{ status: :fail, error: <message> }`.
524
524
 
525
+ Registering a name that is already registered replaces that check in place (the
526
+ same for `register_aggregate_check`), so a `configure` block that runs twice,
527
+ e.g. from `to_prepare` on reload, doesn't run any check twice.
528
+
525
529
  **Per-registration options (0.6.0).** Keywords other than `critical:` and
526
530
  `timeout:` are forwarded to the check's constructor, and validated against its
527
531
  signature at registration — a typo fails at boot, not on every probe:
@@ -86,7 +86,16 @@ module StandardHealth
86
86
  # An optional `StandardHealth::EnvSpec` instance describing required and
87
87
  # recommended environment variables for the host app. Audited via the
88
88
  # /diagnostics/env endpoint.
89
- attr_accessor :env_spec
89
+ attr_reader :env_spec
90
+
91
+ # Validates the spec as it is assigned (`EnvSpec#validate!`), so an
92
+ # `in:` alias with no `mode_alias` fails the initializer at boot rather
93
+ # than raising on the first audit, which is a /diagnostics/env request
94
+ # in production.
95
+ def env_spec=(spec)
96
+ spec.validate! if spec.respond_to?(:validate!)
97
+ @env_spec = spec
98
+ end
90
99
 
91
100
  # --- Instrumentation -----------------------------------------------
92
101
  #
@@ -270,13 +279,16 @@ module StandardHealth
270
279
  # Validated against the constructor's signature HERE, so a typo fails
271
280
  # at boot instead of turning into an ArgumentError on every probe (which
272
281
  # the aggregator would dutifully report as a failing check forever).
282
+ #
283
+ # Re-registering a name replaces that registration in place (last writer
284
+ # wins), so `configure` can safely re-run, e.g. from `to_prepare` on
285
+ # reload, without running the check twice.
273
286
  def register_check(name, klass, critical: false, timeout: nil, **options)
274
287
  validate_check_options!(klass, options)
275
288
  registration = Registration.new(
276
289
  name: name.to_sym, klass: klass, critical: critical, timeout: timeout, options: options
277
290
  )
278
- @checks << registration
279
- registration
291
+ upsert_registration(@checks, registration)
280
292
  end
281
293
 
282
294
  # The check set every consumer app registers by hand. Each entry: default
@@ -362,13 +374,14 @@ module StandardHealth
362
374
  #
363
375
  # c.register_aggregate_check :solid_cable, StandardHealth::Checks::SolidCable
364
376
  # c.register_aggregate_check :attestation_roots, "AttestationRootsCheck"
377
+ #
378
+ # Re-registering a name replaces it in place, as for #register_check.
365
379
  def register_aggregate_check(name, klass, critical: false, timeout: nil, **options)
366
380
  validate_check_options!(klass, options)
367
381
  registration = Registration.new(
368
382
  name: name.to_sym, klass: klass, critical: critical, timeout: timeout, options: options
369
383
  )
370
- @aggregate_checks << registration
371
- registration
384
+ upsert_registration(@aggregate_checks, registration)
372
385
  end
373
386
 
374
387
  DiagnosticsAssertion = Struct.new(:name, :callable)
@@ -440,6 +453,13 @@ module StandardHealth
440
453
 
441
454
  private
442
455
 
456
+ # Replaces the registration with the same name in place, or appends.
457
+ def upsert_registration(registry, registration)
458
+ index = registry.index { |existing| existing.name == registration.name }
459
+ index ? registry[index] = registration : registry << registration
460
+ registration
461
+ end
462
+
443
463
  # Rejects option keys the check's constructor cannot accept. Only
444
464
  # inspects the signature — never instantiates — so a check with side
445
465
  # effects in `initialize` is not run at boot. A constructor taking
@@ -115,6 +115,21 @@ module StandardHealth
115
115
  @mode_aliases[name.to_sym] = Array(modes).map(&:to_s)
116
116
  end
117
117
 
118
+ # Resolves every `in:` alias now. `Configuration#env_spec=` calls this,
119
+ # so an undeclared or misspelt alias fails at boot instead of raising on
120
+ # the first audit (a /diagnostics/env request, or an EnvSpecAudit row).
121
+ #
122
+ # @raise [UnknownModeAlias] naming every unknown alias
123
+ # @return [self]
124
+ def validate!
125
+ unknown = entries.filter_map { |entry| entry.modes if alias_unknown?(entry.modes) }.uniq
126
+ return self if unknown.empty?
127
+
128
+ raise UnknownModeAlias,
129
+ "Unknown mode alias#{"es" if unknown.size > 1}: #{unknown.map(&:inspect).join(", ")} " \
130
+ "(declare it with mode_alias)"
131
+ end
132
+
118
133
  # Group subsequent declarations under a label. Pure metadata —
119
134
  # propagated to audit rows as `group:` and otherwise inert. Nested
120
135
  # `group` blocks are supported; the innermost label is the one that
@@ -253,6 +268,10 @@ module StandardHealth
253
268
  modes.include?(mode_str)
254
269
  end
255
270
 
271
+ def alias_unknown?(modes)
272
+ modes.is_a?(Symbol) && !@mode_aliases.key?(modes)
273
+ end
274
+
256
275
  def resolve_modes(modes)
257
276
  return nil if modes.nil?
258
277
  if modes.is_a?(Symbol)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StandardHealth
4
- VERSION = "0.7.1"
4
+ VERSION = "0.8.0"
5
5
  end
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.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jaryl Sim