@checkstack/healthcheck-backend 1.21.3 → 1.23.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.
- package/CHANGELOG.md +314 -0
- package/drizzle/0021_amazing_wolf_cub.sql +8 -0
- package/drizzle/meta/0021_snapshot.json +717 -0
- package/drizzle/meta/_journal.json +7 -0
- package/package.json +31 -29
- package/src/ai/system-signals-contributor.test.ts +1 -0
- package/src/cache.test.ts +3 -0
- package/src/effective-environments.test.ts +63 -2
- package/src/effective-environments.ts +34 -0
- package/src/health-entity.ts +8 -2
- package/src/health-state.ts +15 -6
- package/src/index.ts +33 -0
- package/src/queue-executor.test.ts +270 -0
- package/src/queue-executor.ts +672 -569
- package/src/router-satellite-ingest.test.ts +136 -0
- package/src/router.ts +65 -4
- package/src/satellite-liveness.test.ts +199 -0
- package/src/satellite-liveness.ts +106 -0
- package/src/schema.ts +21 -0
- package/src/service-batching.test.ts +3 -1
- package/src/service-ingest-assertions.test.ts +33 -60
- package/src/service-paused-filter.test.ts +9 -4
- package/src/service-rollup-worst-wins.test.ts +147 -55
- package/src/service.ts +355 -148
- package/src/state-evaluator.test.ts +49 -0
- package/src/system-health-override.ts +6 -1
- package/tsconfig.json +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,319 @@
|
|
|
1
1
|
# @checkstack/healthcheck-backend
|
|
2
2
|
|
|
3
|
+
## 1.23.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 88f4333: Per-satellite offline threshold, connectivity notifications, and stop satellite-only checks going silent
|
|
8
|
+
|
|
9
|
+
**A satellite going offline was invisible, and so were its checks.** Three
|
|
10
|
+
related changes:
|
|
11
|
+
|
|
12
|
+
**Per-satellite offline threshold.** The 45-second global constant is now a
|
|
13
|
+
per-satellite override (**Offline after**, 2 minutes to 24 hours), because
|
|
14
|
+
tolerance is a property of the link, not of the platform: a satellite on a flaky
|
|
15
|
+
uplink needs grace that should not be forced on every other satellite. The
|
|
16
|
+
threshold is carried on every row read by `computeStatus`, so the entity read,
|
|
17
|
+
the admin list and the heartbeat monitor cannot disagree about the same
|
|
18
|
+
satellite. Additive, nullable column - existing satellites keep the default.
|
|
19
|
+
|
|
20
|
+
**Connectivity notifications.** Satellites are now a notification target with a
|
|
21
|
+
**Satellite connectivity** subscription: a warning when a satellite stops
|
|
22
|
+
heartbeating, informational when it returns. A reconnect only notifies if the
|
|
23
|
+
satellite was actually offline, so a redeploy is not an event. (The same
|
|
24
|
+
transitions remain available as `satellite.heartbeat_lost` / `.connected`
|
|
25
|
+
automation triggers for anyone wanting different routing.)
|
|
26
|
+
|
|
27
|
+
**Satellite-only checks no longer go silent.** BUG FIX: a check with
|
|
28
|
+
`includeLocal: false` whose satellites were all offline recorded NOTHING, so it
|
|
29
|
+
displayed its last known status indefinitely - a dead probe was indistinguishable
|
|
30
|
+
from a passing one. The core now records a `degraded` run with a clear message.
|
|
31
|
+
Degraded rather than unhealthy because the target may be fine; what failed is our
|
|
32
|
+
ability to observe it. Liveness that cannot be resolved is treated as "executing"
|
|
33
|
+
so a transient lookup failure cannot mark the whole fleet degraded at once.
|
|
34
|
+
|
|
35
|
+
Checks also surface staleness: a last run older than five intervals (minimum ten
|
|
36
|
+
minutes) is highlighted, so an ageing status is visible even with no run to
|
|
37
|
+
explain it. Paused checks are never stale, and neither is a RETIRED slice - one
|
|
38
|
+
whose environment was removed or whose satellite was unassigned - because
|
|
39
|
+
warning about something you retired on purpose trains operators to ignore the
|
|
40
|
+
badge.
|
|
41
|
+
|
|
42
|
+
The unobservable run does NOT notify subscribers. One offline satellite degrades
|
|
43
|
+
every check assigned to it in the same tick, and `healthy -> degraded` is an
|
|
44
|
+
escalation, so notifying per check would turn a single root cause into one alert
|
|
45
|
+
per check. The satellite's own connectivity subscription reports the cause once;
|
|
46
|
+
the runs are still recorded, so health and the UI stay honest.
|
|
47
|
+
|
|
48
|
+
Satellite liveness is cached on the shared platform cache with a 5s TTL. The
|
|
49
|
+
executor asks per tick of every satellite-only check and the read is a full
|
|
50
|
+
scan, so the uncached version scaled with the number of such checks. The TTL is
|
|
51
|
+
well below the smallest offline threshold the schema allows, so a cached answer
|
|
52
|
+
can lag a transition by one tick but never span one.
|
|
53
|
+
|
|
54
|
+
Corrects the user guide, which claimed offline satellites produced failed runs -
|
|
55
|
+
they produced nothing at all.
|
|
56
|
+
|
|
57
|
+
### Patch Changes
|
|
58
|
+
|
|
59
|
+
- Updated dependencies [88f4333]
|
|
60
|
+
- Updated dependencies [1deaac5]
|
|
61
|
+
- Updated dependencies [88f4333]
|
|
62
|
+
- Updated dependencies [88f4333]
|
|
63
|
+
- Updated dependencies [88f4333]
|
|
64
|
+
- Updated dependencies [88f4333]
|
|
65
|
+
- Updated dependencies [88f4333]
|
|
66
|
+
- Updated dependencies [1deaac5]
|
|
67
|
+
- Updated dependencies [56e5375]
|
|
68
|
+
- Updated dependencies [88f4333]
|
|
69
|
+
- @checkstack/common@0.24.0
|
|
70
|
+
- @checkstack/healthcheck-common@1.19.1
|
|
71
|
+
- @checkstack/status-page-common@0.7.0
|
|
72
|
+
- @checkstack/incident-common@1.11.0
|
|
73
|
+
- @checkstack/maintenance-common@1.11.0
|
|
74
|
+
- @checkstack/command-backend@0.3.0
|
|
75
|
+
- @checkstack/incident-backend@1.14.0
|
|
76
|
+
- @checkstack/satellite-backend@0.10.0
|
|
77
|
+
- @checkstack/status-page-backend@0.7.0
|
|
78
|
+
- @checkstack/notification-common@1.9.0
|
|
79
|
+
- @checkstack/ai-backend@0.11.5
|
|
80
|
+
- @checkstack/backend-api@0.35.0
|
|
81
|
+
- @checkstack/satellite-common@0.12.0
|
|
82
|
+
- @checkstack/automation-backend@0.11.9
|
|
83
|
+
- @checkstack/secrets-backend@0.3.10
|
|
84
|
+
- @checkstack/ai-common@0.6.8
|
|
85
|
+
- @checkstack/cache-api@0.3.21
|
|
86
|
+
- @checkstack/catalog-backend@1.10.2
|
|
87
|
+
- @checkstack/catalog-common@2.8.2
|
|
88
|
+
- @checkstack/gitops-backend@0.5.28
|
|
89
|
+
- @checkstack/gitops-common@0.7.5
|
|
90
|
+
- @checkstack/healthcheck-execution@0.35.1
|
|
91
|
+
- @checkstack/queue-api@0.4.1
|
|
92
|
+
- @checkstack/script-packages-backend@0.4.7
|
|
93
|
+
- @checkstack/sdk@0.136.1
|
|
94
|
+
- @checkstack/secrets-common@0.3.4
|
|
95
|
+
- @checkstack/signal-common@0.3.2
|
|
96
|
+
- @checkstack/cache-utils@0.3.2
|
|
97
|
+
|
|
98
|
+
## 1.22.0
|
|
99
|
+
|
|
100
|
+
### Minor Changes
|
|
101
|
+
|
|
102
|
+
- be74b01: Evaluate health per probe location, so a failing satellite can no longer read as healthy
|
|
103
|
+
|
|
104
|
+
Thanks to @stuajnht for reporting: a system whose local check succeeded and
|
|
105
|
+
whose satellite check failed was shown as **healthy**, and the report correctly
|
|
106
|
+
guessed the cause - one combined verdict where there should have been one per
|
|
107
|
+
location.
|
|
108
|
+
|
|
109
|
+
A check's runs were grouped into slices by environment alone, so both locations'
|
|
110
|
+
runs landed in the same slice and were handed to the threshold evaluator as one
|
|
111
|
+
interleaved stream. In the default `consecutive` mode the streak breaks on every
|
|
112
|
+
alternation, no threshold is ever reached, and evaluation falls through to its
|
|
113
|
+
healthy default. A satellite failing 100% of the time was therefore invisible
|
|
114
|
+
for as long as a local check succeeded between its runs.
|
|
115
|
+
|
|
116
|
+
A slice is now an **(environment, source)** pair - one environment as probed
|
|
117
|
+
from one location - and each is evaluated on its own window, with the worst
|
|
118
|
+
result deciding the check. This is the same rule environments already followed;
|
|
119
|
+
the source dimension was simply never considered. Both the system rollup and the
|
|
120
|
+
system overview were affected, and both are fixed.
|
|
121
|
+
|
|
122
|
+
Related correctness fixes that fall out of keying slices by source:
|
|
123
|
+
|
|
124
|
+
- A **de-assigned satellite** (or the core after **Include local** is turned
|
|
125
|
+
off) stops counting immediately instead of dragging the rollup with its last
|
|
126
|
+
failures until they age out of the window. Its history moves under **Old
|
|
127
|
+
checks**.
|
|
128
|
+
- **Per-satellite environment scoping** is honoured when resolving slices, so a
|
|
129
|
+
satellite narrowed to production no longer keeps a stale staging slice alive.
|
|
130
|
+
- A satellite scoped to run env-less while the core fans out keeps its slice
|
|
131
|
+
live; the "has a live environment slice" question is now answered per
|
|
132
|
+
location, as the backend already did.
|
|
133
|
+
|
|
134
|
+
The system overview shows one row per slice and names the location (for example
|
|
135
|
+
**EU West**) as soon as a check runs from more than one place. A check that only
|
|
136
|
+
ever runs on the core shows no location label - there is nothing to
|
|
137
|
+
disambiguate.
|
|
138
|
+
|
|
139
|
+
`checkStatuses[].slices` and the overview's per-slice entries carry the
|
|
140
|
+
breakdown (`sourceId`, `sourceLabel`, `sourceOrphaned`) on the wire, and
|
|
141
|
+
`sliceCount` / `failingSliceCount` now count locations as well as environments -
|
|
142
|
+
so a check probing one environment from the core and one satellite contributes
|
|
143
|
+
2 to the dashboard's "X of Y checks failing" denominator, not 1.
|
|
144
|
+
|
|
145
|
+
- be74b01: Satellites run per environment, and can be scoped to specific ones
|
|
146
|
+
|
|
147
|
+
Satellites were handed no environment information at all, so every result they
|
|
148
|
+
reported was stored env-less. On a system with environments that meant satellite
|
|
149
|
+
checks contributed nothing to per-environment health - and, until the preceding
|
|
150
|
+
fix, were labelled "Old checks" for it.
|
|
151
|
+
|
|
152
|
+
A satellite now fans out exactly as the local executor does:
|
|
153
|
+
|
|
154
|
+
- `getAssignmentsForSatellite` resolves each assignment's effective environments
|
|
155
|
+
and sends them with the assignment.
|
|
156
|
+
- The agent schedules ONE run per environment and reports each result with its
|
|
157
|
+
`environmentId`, so per-environment history, charts and rollups include
|
|
158
|
+
satellite results.
|
|
159
|
+
- Collectors on a satellite now receive the `environment` run-context block, so
|
|
160
|
+
`{{ environment.<key> }}` templating resolves there exactly as it does locally.
|
|
161
|
+
|
|
162
|
+
**A satellite can also be scoped to specific environments.** Without that, every
|
|
163
|
+
satellite would probe every environment - a staging-network satellite would start
|
|
164
|
+
failing prod checks it has no route to, and one per-environment slice would merge
|
|
165
|
+
results from satellites in different networks. A new `satelliteEnvironmentIds`
|
|
166
|
+
map on the assignment scopes each satellite: an absent key means "all
|
|
167
|
+
environments" (so every existing assignment behaves exactly as before), `[]` means
|
|
168
|
+
one env-less run, and a list narrows to those ids. A satellite can only ever
|
|
169
|
+
narrow the assignment's own selector, never widen it.
|
|
170
|
+
|
|
171
|
+
Both protocol additions are optional, for version skew in either direction: an
|
|
172
|
+
older satellite sends no `environmentId` and its runs are stored env-less as they
|
|
173
|
+
always were, while an older core sends no environments and the agent falls back to
|
|
174
|
+
a single env-less run.
|
|
175
|
+
|
|
176
|
+
The assignment's Execution panel gains a per-satellite environment picker,
|
|
177
|
+
shown for each assigned satellite once the system has environments.
|
|
178
|
+
|
|
179
|
+
Thanks to [@stuajnht](https://github.com/stuajnht) for the valuable feedback.
|
|
180
|
+
|
|
181
|
+
- be74b01: Drive satellite health results through the same reactive/notify path as local runs
|
|
182
|
+
|
|
183
|
+
A satellite-detected health change previously did almost nothing on the core:
|
|
184
|
+
`ingestSatelliteResult` inserted the run row and invalidated the cache, and
|
|
185
|
+
stopped there. A LOCAL run additionally drives the whole reactive layer - the
|
|
186
|
+
`health` entity write (which fires the ENTITY_CHANGED that automations and
|
|
187
|
+
triggers key on), the state-transition record, the subscriber notification, the
|
|
188
|
+
checkCompleted/checkFailed automation hooks, and the realtime signals. So a
|
|
189
|
+
satellite that detected an outage fired **no notifications, no automations, no
|
|
190
|
+
transition record, and no realtime signal** - satellite monitoring was
|
|
191
|
+
effectively silent.
|
|
192
|
+
|
|
193
|
+
Both paths now run through ONE shared function, `persistRunAndReact`, so a
|
|
194
|
+
satellite result reacts exactly like a local one. The host binds the service
|
|
195
|
+
dependencies once and hands the router a narrowed reactor, so the local and
|
|
196
|
+
satellite callers cannot pass different dependencies and drift apart again
|
|
197
|
+
(`ingestSatelliteResult` was itself a duplicated-and-drifted copy of the local
|
|
198
|
+
persistence path - this removes the duplication that caused it). Ingest now
|
|
199
|
+
splits into `processSatelliteResult` (evaluate assertions, strip ephemeral
|
|
200
|
+
fields, resolve the check name) plus the shared reactive path.
|
|
201
|
+
|
|
202
|
+
Also fixed: a satellite collector's transport error is now annotated as
|
|
203
|
+
`_collectorError` on the stored result, matching a local run - the satellite
|
|
204
|
+
previously dropped that annotation.
|
|
205
|
+
|
|
206
|
+
Coverage: added tests that a satellite result is routed through the shared
|
|
207
|
+
reactor with its processed payload (guarding against a silent regression back
|
|
208
|
+
to insert-only), and extracted the satellite's `executeAssignment` into a
|
|
209
|
+
testable module with tests for custom-field template expansion, probe-measured
|
|
210
|
+
timings, the `_collectorError` annotation, and the strategy-not-loaded path.
|
|
211
|
+
|
|
212
|
+
- be74b01: Expand system/environment custom fields in satellite health checks, via one shared execution engine
|
|
213
|
+
|
|
214
|
+
Thanks to @stuajnht for reporting: a system or environment custom field
|
|
215
|
+
referenced with `{{ system.metadata.<key> }}` / `{{ environment.<key> }}` in a
|
|
216
|
+
health check was NOT expanded when the check ran on a satellite - the raw
|
|
217
|
+
template reached the probe. The core queue executor grew a per-run templating
|
|
218
|
+
pass, but the satellite's execution loop was a hand-maintained COPY that never
|
|
219
|
+
did, so the two drifted.
|
|
220
|
+
|
|
221
|
+
The fix removes the copy. A new lean package `@checkstack/healthcheck-execution`
|
|
222
|
+
owns the shared execution engine - render the strategy + collector
|
|
223
|
+
`x-templatable` fields against the run's environment/system context, build the
|
|
224
|
+
transport client, run the collectors, close the client - and BOTH the core
|
|
225
|
+
queue executor and the satellite now run through it. Templating, the
|
|
226
|
+
secret-then-template ordering, and the per-collector fan-out therefore cannot
|
|
227
|
+
drift between core and satellite again. Each side keeps only its genuine edges
|
|
228
|
+
as injected hooks: the core resolves secrets from its database and does
|
|
229
|
+
migrate-on-read; the satellite resolves them just-in-time over its socket.
|
|
230
|
+
|
|
231
|
+
Also fixed: transport sub-phase timings (DNS / connect / TLS / wait / transfer)
|
|
232
|
+
are now measured AT THE PROBE and reported by satellites, so a satellite run's
|
|
233
|
+
`metadata.timings` matches a local run's. The core cannot derive the timing of a
|
|
234
|
+
probe it did not run - and may have no route to a target a satellite can reach -
|
|
235
|
+
so the satellite must produce these; the core persists them as-is.
|
|
236
|
+
|
|
237
|
+
- be74b01: Stop reporting systems as healthy when nothing has measured them
|
|
238
|
+
|
|
239
|
+
A system whose health check had never produced a run reported `healthy` - so it
|
|
240
|
+
showed green in the catalog, kept its group green, and read "operational" on the
|
|
241
|
+
public status page. A system with no checks at all did the same. For a
|
|
242
|
+
monitoring product that is the worst possible default: the one state you must
|
|
243
|
+
never invent is the reassuring one.
|
|
244
|
+
|
|
245
|
+
`getSystemHealthStatus` began each check at `healthy` and each system's
|
|
246
|
+
aggregate at `healthy`, then only ever downgraded. With no runs to examine,
|
|
247
|
+
nothing downgraded them. `HealthCheckStatus` had no way to say "not measured".
|
|
248
|
+
|
|
249
|
+
A new `SystemHealthStatus` adds `unknown` for systems and their checks. It is
|
|
250
|
+
deliberately NOT a run status - a run that happened is always healthy, degraded
|
|
251
|
+
or unhealthy, and the database enum stays three-valued. Now:
|
|
252
|
+
|
|
253
|
+
- A check with no runs is `unknown`, not `healthy`.
|
|
254
|
+
- A system reports `unknown` when no check contributed a signal. A system with
|
|
255
|
+
one healthy check and one never-run check still reads `healthy`: it has
|
|
256
|
+
positive evidence, and the unmeasured check is visible on its own page.
|
|
257
|
+
- The catalog reports `unknown` by OMISSION, which its group rollup already
|
|
258
|
+
treats as "no signal" - so a group with an unmeasured member stops claiming to
|
|
259
|
+
be healthy. That is the reported bug.
|
|
260
|
+
- The public status page maps it to its existing `unknown`, which is ignored for
|
|
261
|
+
the overall banner unless everything is unknown. One unmeasured system no
|
|
262
|
+
longer claims "operational" for itself, and does not panic the whole page.
|
|
263
|
+
- A first measurement records a transition with a NULL `fromStatus` - the column
|
|
264
|
+
was already nullable for exactly this case - instead of pretending the system
|
|
265
|
+
was healthy beforehand.
|
|
266
|
+
- Automations matching on `unhealthy` do not fire for a merely unmeasured
|
|
267
|
+
system, which is correct: an unmeasured system is not a detected outage.
|
|
268
|
+
|
|
269
|
+
Dependency warnings deliberately keep their current behaviour: an unmeasured
|
|
270
|
+
upstream raises no warning, and a never-run check is dropped from the evaluation
|
|
271
|
+
rather than counted as passing.
|
|
272
|
+
|
|
273
|
+
Note that pausing a system's only check now leaves it `unknown` rather than
|
|
274
|
+
`healthy`. Paused failures still do not keep a system degraded - that behaviour
|
|
275
|
+
is unchanged - but with nothing running, the system is genuinely unmeasured.
|
|
276
|
+
|
|
277
|
+
Thanks to [@stuajnht](https://github.com/stuajnht) for the valuable feedback.
|
|
278
|
+
|
|
279
|
+
### Patch Changes
|
|
280
|
+
|
|
281
|
+
- Updated dependencies [be74b01]
|
|
282
|
+
- Updated dependencies [be74b01]
|
|
283
|
+
- Updated dependencies [be74b01]
|
|
284
|
+
- Updated dependencies [be74b01]
|
|
285
|
+
- Updated dependencies [be74b01]
|
|
286
|
+
- Updated dependencies [be74b01]
|
|
287
|
+
- Updated dependencies [be74b01]
|
|
288
|
+
- Updated dependencies [be74b01]
|
|
289
|
+
- Updated dependencies [be74b01]
|
|
290
|
+
- Updated dependencies [be74b01]
|
|
291
|
+
- Updated dependencies [be74b01]
|
|
292
|
+
- Updated dependencies [be74b01]
|
|
293
|
+
- Updated dependencies [be74b01]
|
|
294
|
+
- Updated dependencies [be74b01]
|
|
295
|
+
- Updated dependencies [be74b01]
|
|
296
|
+
- Updated dependencies [be74b01]
|
|
297
|
+
- @checkstack/ai-backend@0.11.4
|
|
298
|
+
- @checkstack/notification-common@1.8.0
|
|
299
|
+
- @checkstack/incident-backend@1.13.6
|
|
300
|
+
- @checkstack/healthcheck-common@1.19.0
|
|
301
|
+
- @checkstack/satellite-backend@0.9.4
|
|
302
|
+
- @checkstack/healthcheck-execution@0.35.0
|
|
303
|
+
- @checkstack/status-page-backend@0.6.6
|
|
304
|
+
- @checkstack/status-page-common@0.6.5
|
|
305
|
+
- @checkstack/automation-backend@0.11.8
|
|
306
|
+
- @checkstack/secrets-backend@0.3.9
|
|
307
|
+
- @checkstack/catalog-backend@1.10.1
|
|
308
|
+
- @checkstack/catalog-common@2.8.1
|
|
309
|
+
- @checkstack/incident-common@1.10.5
|
|
310
|
+
- @checkstack/maintenance-common@1.10.5
|
|
311
|
+
- @checkstack/script-packages-backend@0.4.6
|
|
312
|
+
- @checkstack/sdk@0.135.1
|
|
313
|
+
- @checkstack/backend-api@0.34.1
|
|
314
|
+
- @checkstack/command-backend@0.2.27
|
|
315
|
+
- @checkstack/gitops-backend@0.5.27
|
|
316
|
+
|
|
3
317
|
## 1.21.3
|
|
4
318
|
|
|
5
319
|
### Patch Changes
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
-- Per-satellite environment scoping for a health-check assignment.
|
|
2
|
+
--
|
|
3
|
+
-- Deliberately NO backfill: the column's "key absent" semantics already mean
|
|
4
|
+
-- "this satellite runs every environment the assignment resolves to", so a NULL
|
|
5
|
+
-- column reproduces exactly the behaviour every existing row has today. Writing
|
|
6
|
+
-- an explicit map for every row would only freeze today's satellite list into
|
|
7
|
+
-- data that has to be maintained.
|
|
8
|
+
ALTER TABLE "system_health_checks" ADD COLUMN "satellite_environment_ids" jsonb;
|