@checkstack/healthcheck-backend 1.21.2 → 1.22.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 +289 -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 +30 -31
- 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 +53 -14
- package/src/queue-executor.ts +554 -578
- package/src/realtime-aggregation.test.ts +10 -11
- package/src/realtime-aggregation.ts +12 -50
- package/src/router-satellite-ingest.test.ts +136 -0
- package/src/router.ts +65 -4
- 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 +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,294 @@
|
|
|
1
1
|
# @checkstack/healthcheck-backend
|
|
2
2
|
|
|
3
|
+
## 1.22.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- be74b01: Evaluate health per probe location, so a failing satellite can no longer read as healthy
|
|
8
|
+
|
|
9
|
+
Thanks to @stuajnht for reporting: a system whose local check succeeded and
|
|
10
|
+
whose satellite check failed was shown as **healthy**, and the report correctly
|
|
11
|
+
guessed the cause - one combined verdict where there should have been one per
|
|
12
|
+
location.
|
|
13
|
+
|
|
14
|
+
A check's runs were grouped into slices by environment alone, so both locations'
|
|
15
|
+
runs landed in the same slice and were handed to the threshold evaluator as one
|
|
16
|
+
interleaved stream. In the default `consecutive` mode the streak breaks on every
|
|
17
|
+
alternation, no threshold is ever reached, and evaluation falls through to its
|
|
18
|
+
healthy default. A satellite failing 100% of the time was therefore invisible
|
|
19
|
+
for as long as a local check succeeded between its runs.
|
|
20
|
+
|
|
21
|
+
A slice is now an **(environment, source)** pair - one environment as probed
|
|
22
|
+
from one location - and each is evaluated on its own window, with the worst
|
|
23
|
+
result deciding the check. This is the same rule environments already followed;
|
|
24
|
+
the source dimension was simply never considered. Both the system rollup and the
|
|
25
|
+
system overview were affected, and both are fixed.
|
|
26
|
+
|
|
27
|
+
Related correctness fixes that fall out of keying slices by source:
|
|
28
|
+
|
|
29
|
+
- A **de-assigned satellite** (or the core after **Include local** is turned
|
|
30
|
+
off) stops counting immediately instead of dragging the rollup with its last
|
|
31
|
+
failures until they age out of the window. Its history moves under **Old
|
|
32
|
+
checks**.
|
|
33
|
+
- **Per-satellite environment scoping** is honoured when resolving slices, so a
|
|
34
|
+
satellite narrowed to production no longer keeps a stale staging slice alive.
|
|
35
|
+
- A satellite scoped to run env-less while the core fans out keeps its slice
|
|
36
|
+
live; the "has a live environment slice" question is now answered per
|
|
37
|
+
location, as the backend already did.
|
|
38
|
+
|
|
39
|
+
The system overview shows one row per slice and names the location (for example
|
|
40
|
+
**EU West**) as soon as a check runs from more than one place. A check that only
|
|
41
|
+
ever runs on the core shows no location label - there is nothing to
|
|
42
|
+
disambiguate.
|
|
43
|
+
|
|
44
|
+
`checkStatuses[].slices` and the overview's per-slice entries carry the
|
|
45
|
+
breakdown (`sourceId`, `sourceLabel`, `sourceOrphaned`) on the wire, and
|
|
46
|
+
`sliceCount` / `failingSliceCount` now count locations as well as environments -
|
|
47
|
+
so a check probing one environment from the core and one satellite contributes
|
|
48
|
+
2 to the dashboard's "X of Y checks failing" denominator, not 1.
|
|
49
|
+
|
|
50
|
+
- be74b01: Satellites run per environment, and can be scoped to specific ones
|
|
51
|
+
|
|
52
|
+
Satellites were handed no environment information at all, so every result they
|
|
53
|
+
reported was stored env-less. On a system with environments that meant satellite
|
|
54
|
+
checks contributed nothing to per-environment health - and, until the preceding
|
|
55
|
+
fix, were labelled "Old checks" for it.
|
|
56
|
+
|
|
57
|
+
A satellite now fans out exactly as the local executor does:
|
|
58
|
+
|
|
59
|
+
- `getAssignmentsForSatellite` resolves each assignment's effective environments
|
|
60
|
+
and sends them with the assignment.
|
|
61
|
+
- The agent schedules ONE run per environment and reports each result with its
|
|
62
|
+
`environmentId`, so per-environment history, charts and rollups include
|
|
63
|
+
satellite results.
|
|
64
|
+
- Collectors on a satellite now receive the `environment` run-context block, so
|
|
65
|
+
`{{ environment.<key> }}` templating resolves there exactly as it does locally.
|
|
66
|
+
|
|
67
|
+
**A satellite can also be scoped to specific environments.** Without that, every
|
|
68
|
+
satellite would probe every environment - a staging-network satellite would start
|
|
69
|
+
failing prod checks it has no route to, and one per-environment slice would merge
|
|
70
|
+
results from satellites in different networks. A new `satelliteEnvironmentIds`
|
|
71
|
+
map on the assignment scopes each satellite: an absent key means "all
|
|
72
|
+
environments" (so every existing assignment behaves exactly as before), `[]` means
|
|
73
|
+
one env-less run, and a list narrows to those ids. A satellite can only ever
|
|
74
|
+
narrow the assignment's own selector, never widen it.
|
|
75
|
+
|
|
76
|
+
Both protocol additions are optional, for version skew in either direction: an
|
|
77
|
+
older satellite sends no `environmentId` and its runs are stored env-less as they
|
|
78
|
+
always were, while an older core sends no environments and the agent falls back to
|
|
79
|
+
a single env-less run.
|
|
80
|
+
|
|
81
|
+
The assignment's Execution panel gains a per-satellite environment picker,
|
|
82
|
+
shown for each assigned satellite once the system has environments.
|
|
83
|
+
|
|
84
|
+
Thanks to [@stuajnht](https://github.com/stuajnht) for the valuable feedback.
|
|
85
|
+
|
|
86
|
+
- be74b01: Drive satellite health results through the same reactive/notify path as local runs
|
|
87
|
+
|
|
88
|
+
A satellite-detected health change previously did almost nothing on the core:
|
|
89
|
+
`ingestSatelliteResult` inserted the run row and invalidated the cache, and
|
|
90
|
+
stopped there. A LOCAL run additionally drives the whole reactive layer - the
|
|
91
|
+
`health` entity write (which fires the ENTITY_CHANGED that automations and
|
|
92
|
+
triggers key on), the state-transition record, the subscriber notification, the
|
|
93
|
+
checkCompleted/checkFailed automation hooks, and the realtime signals. So a
|
|
94
|
+
satellite that detected an outage fired **no notifications, no automations, no
|
|
95
|
+
transition record, and no realtime signal** - satellite monitoring was
|
|
96
|
+
effectively silent.
|
|
97
|
+
|
|
98
|
+
Both paths now run through ONE shared function, `persistRunAndReact`, so a
|
|
99
|
+
satellite result reacts exactly like a local one. The host binds the service
|
|
100
|
+
dependencies once and hands the router a narrowed reactor, so the local and
|
|
101
|
+
satellite callers cannot pass different dependencies and drift apart again
|
|
102
|
+
(`ingestSatelliteResult` was itself a duplicated-and-drifted copy of the local
|
|
103
|
+
persistence path - this removes the duplication that caused it). Ingest now
|
|
104
|
+
splits into `processSatelliteResult` (evaluate assertions, strip ephemeral
|
|
105
|
+
fields, resolve the check name) plus the shared reactive path.
|
|
106
|
+
|
|
107
|
+
Also fixed: a satellite collector's transport error is now annotated as
|
|
108
|
+
`_collectorError` on the stored result, matching a local run - the satellite
|
|
109
|
+
previously dropped that annotation.
|
|
110
|
+
|
|
111
|
+
Coverage: added tests that a satellite result is routed through the shared
|
|
112
|
+
reactor with its processed payload (guarding against a silent regression back
|
|
113
|
+
to insert-only), and extracted the satellite's `executeAssignment` into a
|
|
114
|
+
testable module with tests for custom-field template expansion, probe-measured
|
|
115
|
+
timings, the `_collectorError` annotation, and the strategy-not-loaded path.
|
|
116
|
+
|
|
117
|
+
- be74b01: Expand system/environment custom fields in satellite health checks, via one shared execution engine
|
|
118
|
+
|
|
119
|
+
Thanks to @stuajnht for reporting: a system or environment custom field
|
|
120
|
+
referenced with `{{ system.metadata.<key> }}` / `{{ environment.<key> }}` in a
|
|
121
|
+
health check was NOT expanded when the check ran on a satellite - the raw
|
|
122
|
+
template reached the probe. The core queue executor grew a per-run templating
|
|
123
|
+
pass, but the satellite's execution loop was a hand-maintained COPY that never
|
|
124
|
+
did, so the two drifted.
|
|
125
|
+
|
|
126
|
+
The fix removes the copy. A new lean package `@checkstack/healthcheck-execution`
|
|
127
|
+
owns the shared execution engine - render the strategy + collector
|
|
128
|
+
`x-templatable` fields against the run's environment/system context, build the
|
|
129
|
+
transport client, run the collectors, close the client - and BOTH the core
|
|
130
|
+
queue executor and the satellite now run through it. Templating, the
|
|
131
|
+
secret-then-template ordering, and the per-collector fan-out therefore cannot
|
|
132
|
+
drift between core and satellite again. Each side keeps only its genuine edges
|
|
133
|
+
as injected hooks: the core resolves secrets from its database and does
|
|
134
|
+
migrate-on-read; the satellite resolves them just-in-time over its socket.
|
|
135
|
+
|
|
136
|
+
Also fixed: transport sub-phase timings (DNS / connect / TLS / wait / transfer)
|
|
137
|
+
are now measured AT THE PROBE and reported by satellites, so a satellite run's
|
|
138
|
+
`metadata.timings` matches a local run's. The core cannot derive the timing of a
|
|
139
|
+
probe it did not run - and may have no route to a target a satellite can reach -
|
|
140
|
+
so the satellite must produce these; the core persists them as-is.
|
|
141
|
+
|
|
142
|
+
- be74b01: Stop reporting systems as healthy when nothing has measured them
|
|
143
|
+
|
|
144
|
+
A system whose health check had never produced a run reported `healthy` - so it
|
|
145
|
+
showed green in the catalog, kept its group green, and read "operational" on the
|
|
146
|
+
public status page. A system with no checks at all did the same. For a
|
|
147
|
+
monitoring product that is the worst possible default: the one state you must
|
|
148
|
+
never invent is the reassuring one.
|
|
149
|
+
|
|
150
|
+
`getSystemHealthStatus` began each check at `healthy` and each system's
|
|
151
|
+
aggregate at `healthy`, then only ever downgraded. With no runs to examine,
|
|
152
|
+
nothing downgraded them. `HealthCheckStatus` had no way to say "not measured".
|
|
153
|
+
|
|
154
|
+
A new `SystemHealthStatus` adds `unknown` for systems and their checks. It is
|
|
155
|
+
deliberately NOT a run status - a run that happened is always healthy, degraded
|
|
156
|
+
or unhealthy, and the database enum stays three-valued. Now:
|
|
157
|
+
|
|
158
|
+
- A check with no runs is `unknown`, not `healthy`.
|
|
159
|
+
- A system reports `unknown` when no check contributed a signal. A system with
|
|
160
|
+
one healthy check and one never-run check still reads `healthy`: it has
|
|
161
|
+
positive evidence, and the unmeasured check is visible on its own page.
|
|
162
|
+
- The catalog reports `unknown` by OMISSION, which its group rollup already
|
|
163
|
+
treats as "no signal" - so a group with an unmeasured member stops claiming to
|
|
164
|
+
be healthy. That is the reported bug.
|
|
165
|
+
- The public status page maps it to its existing `unknown`, which is ignored for
|
|
166
|
+
the overall banner unless everything is unknown. One unmeasured system no
|
|
167
|
+
longer claims "operational" for itself, and does not panic the whole page.
|
|
168
|
+
- A first measurement records a transition with a NULL `fromStatus` - the column
|
|
169
|
+
was already nullable for exactly this case - instead of pretending the system
|
|
170
|
+
was healthy beforehand.
|
|
171
|
+
- Automations matching on `unhealthy` do not fire for a merely unmeasured
|
|
172
|
+
system, which is correct: an unmeasured system is not a detected outage.
|
|
173
|
+
|
|
174
|
+
Dependency warnings deliberately keep their current behaviour: an unmeasured
|
|
175
|
+
upstream raises no warning, and a never-run check is dropped from the evaluation
|
|
176
|
+
rather than counted as passing.
|
|
177
|
+
|
|
178
|
+
Note that pausing a system's only check now leaves it `unknown` rather than
|
|
179
|
+
`healthy`. Paused failures still do not keep a system degraded - that behaviour
|
|
180
|
+
is unchanged - but with nothing running, the system is genuinely unmeasured.
|
|
181
|
+
|
|
182
|
+
Thanks to [@stuajnht](https://github.com/stuajnht) for the valuable feedback.
|
|
183
|
+
|
|
184
|
+
### Patch Changes
|
|
185
|
+
|
|
186
|
+
- Updated dependencies [be74b01]
|
|
187
|
+
- Updated dependencies [be74b01]
|
|
188
|
+
- Updated dependencies [be74b01]
|
|
189
|
+
- Updated dependencies [be74b01]
|
|
190
|
+
- Updated dependencies [be74b01]
|
|
191
|
+
- Updated dependencies [be74b01]
|
|
192
|
+
- Updated dependencies [be74b01]
|
|
193
|
+
- Updated dependencies [be74b01]
|
|
194
|
+
- Updated dependencies [be74b01]
|
|
195
|
+
- Updated dependencies [be74b01]
|
|
196
|
+
- Updated dependencies [be74b01]
|
|
197
|
+
- Updated dependencies [be74b01]
|
|
198
|
+
- Updated dependencies [be74b01]
|
|
199
|
+
- Updated dependencies [be74b01]
|
|
200
|
+
- Updated dependencies [be74b01]
|
|
201
|
+
- Updated dependencies [be74b01]
|
|
202
|
+
- @checkstack/ai-backend@0.11.4
|
|
203
|
+
- @checkstack/notification-common@1.8.0
|
|
204
|
+
- @checkstack/incident-backend@1.13.6
|
|
205
|
+
- @checkstack/healthcheck-common@1.19.0
|
|
206
|
+
- @checkstack/satellite-backend@0.9.4
|
|
207
|
+
- @checkstack/healthcheck-execution@0.35.0
|
|
208
|
+
- @checkstack/status-page-backend@0.6.6
|
|
209
|
+
- @checkstack/status-page-common@0.6.5
|
|
210
|
+
- @checkstack/automation-backend@0.11.8
|
|
211
|
+
- @checkstack/secrets-backend@0.3.9
|
|
212
|
+
- @checkstack/catalog-backend@1.10.1
|
|
213
|
+
- @checkstack/catalog-common@2.8.1
|
|
214
|
+
- @checkstack/incident-common@1.10.5
|
|
215
|
+
- @checkstack/maintenance-common@1.10.5
|
|
216
|
+
- @checkstack/script-packages-backend@0.4.6
|
|
217
|
+
- @checkstack/sdk@0.135.1
|
|
218
|
+
- @checkstack/backend-api@0.34.1
|
|
219
|
+
- @checkstack/command-backend@0.2.27
|
|
220
|
+
- @checkstack/gitops-backend@0.5.27
|
|
221
|
+
|
|
222
|
+
## 1.21.3
|
|
223
|
+
|
|
224
|
+
### Patch Changes
|
|
225
|
+
|
|
226
|
+
- 6c8b36b: Promote the health-check run-queue contract and the observability window
|
|
227
|
+
math into `@checkstack/healthcheck-common`: `HEALTH_CHECK_QUEUE`,
|
|
228
|
+
`HealthCheckJobPayload`, `fastPathJobId` (per-plugin prefix) and
|
|
229
|
+
`computeWindowBounds`/`computeSecondsSinceLast` now have ONE definition
|
|
230
|
+
that the queue owner (healthcheck-backend) and every observability
|
|
231
|
+
strategy plugin import, replacing the per-plugin mirror copies that had
|
|
232
|
+
to be kept in lock-step by convention. Enqueued job ids and window
|
|
233
|
+
semantics are byte-identical; this is a drift-proofing refactor, not a
|
|
234
|
+
behavior change.
|
|
235
|
+
- 6c8b36b: Run the config-secrets backfill in afterPluginsReady instead of init.
|
|
236
|
+
Health-check strategies contributed by other plugins register during THEIR
|
|
237
|
+
init, and plugin init order follows the service-ref graph, so running the
|
|
238
|
+
backfill during healthcheck's own init could scan configurations before a
|
|
239
|
+
contributor (e.g. logstream's health strategy) had registered - skipping
|
|
240
|
+
that strategy's config with a "strategy not registered" warning at boot.
|
|
241
|
+
Only afterPluginsReady guarantees a complete registry. The backfill is
|
|
242
|
+
idempotent, so any configuration skipped by an earlier boot is picked up
|
|
243
|
+
on the next one.
|
|
244
|
+
- 6c8b36b: Promote the t-digest percentile helpers from healthcheck-backend into
|
|
245
|
+
backend-api (`createTDigest`, `serializeTDigest`, `deserializeTDigest`,
|
|
246
|
+
`mergeTDigestStates`, `percentileFromState`, ...), so any plugin can maintain
|
|
247
|
+
mergeable percentile sketches; tracestream's per-operation p95 buckets are the
|
|
248
|
+
first new consumer. healthcheck-backend now imports the shared module (the
|
|
249
|
+
local copy is removed, no behavior change).
|
|
250
|
+
- Updated dependencies [6c8b36b]
|
|
251
|
+
- Updated dependencies [6c8b36b]
|
|
252
|
+
- Updated dependencies [6c8b36b]
|
|
253
|
+
- Updated dependencies [6c8b36b]
|
|
254
|
+
- Updated dependencies [6c8b36b]
|
|
255
|
+
- Updated dependencies [6c8b36b]
|
|
256
|
+
- Updated dependencies [6c8b36b]
|
|
257
|
+
- Updated dependencies [6c8b36b]
|
|
258
|
+
- Updated dependencies [6c8b36b]
|
|
259
|
+
- Updated dependencies [6c8b36b]
|
|
260
|
+
- Updated dependencies [6c8b36b]
|
|
261
|
+
- Updated dependencies [6c8b36b]
|
|
262
|
+
- Updated dependencies [6c8b36b]
|
|
263
|
+
- Updated dependencies [6c8b36b]
|
|
264
|
+
- Updated dependencies [6c8b36b]
|
|
265
|
+
- @checkstack/ai-backend@0.11.3
|
|
266
|
+
- @checkstack/backend-api@0.34.0
|
|
267
|
+
- @checkstack/healthcheck-common@1.18.0
|
|
268
|
+
- @checkstack/catalog-common@2.8.0
|
|
269
|
+
- @checkstack/queue-api@0.4.0
|
|
270
|
+
- @checkstack/common@0.23.0
|
|
271
|
+
- @checkstack/catalog-backend@1.10.0
|
|
272
|
+
- @checkstack/status-page-backend@0.6.5
|
|
273
|
+
- @checkstack/automation-backend@0.11.7
|
|
274
|
+
- @checkstack/incident-backend@1.13.5
|
|
275
|
+
- @checkstack/script-packages-backend@0.4.5
|
|
276
|
+
- @checkstack/sdk@0.133.1
|
|
277
|
+
- @checkstack/command-backend@0.2.26
|
|
278
|
+
- @checkstack/gitops-backend@0.5.26
|
|
279
|
+
- @checkstack/satellite-backend@0.9.3
|
|
280
|
+
- @checkstack/secrets-backend@0.3.8
|
|
281
|
+
- @checkstack/incident-common@1.10.4
|
|
282
|
+
- @checkstack/maintenance-common@1.10.4
|
|
283
|
+
- @checkstack/status-page-common@0.6.4
|
|
284
|
+
- @checkstack/ai-common@0.6.7
|
|
285
|
+
- @checkstack/cache-api@0.3.20
|
|
286
|
+
- @checkstack/gitops-common@0.7.4
|
|
287
|
+
- @checkstack/notification-common@1.7.2
|
|
288
|
+
- @checkstack/secrets-common@0.3.3
|
|
289
|
+
- @checkstack/signal-common@0.3.1
|
|
290
|
+
- @checkstack/cache-utils@0.3.1
|
|
291
|
+
|
|
3
292
|
## 1.21.2
|
|
4
293
|
|
|
5
294
|
### 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;
|