@checkstack/incident-common 1.7.2 → 1.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.
- package/CHANGELOG.md +36 -0
- package/package.json +1 -1
- package/src/index.ts +4 -0
- package/src/rpc-contract.ts +34 -0
- package/src/schemas.ts +33 -0
- package/src/signals.test.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# @checkstack/incident-common
|
|
2
2
|
|
|
3
|
+
## 1.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9d30324: Incidents can now optionally override the health status of their affected
|
|
8
|
+
systems. When creating or editing an incident you can pick "Override system
|
|
9
|
+
health" (Degraded or Unhealthy); while the incident is active (not resolved)
|
|
10
|
+
that status is folded into every affected system's derived health via
|
|
11
|
+
worst-wins, so it shows on every health surface (status pages, dashboards,
|
|
12
|
+
dependency map, catalog badges). A health check reporting a worse status still
|
|
13
|
+
wins, and the override lifts automatically when the incident resolves. This
|
|
14
|
+
covers components that no automated check can monitor (e.g. a running app whose
|
|
15
|
+
licenses were revoked so it won't open).
|
|
16
|
+
|
|
17
|
+
The override is a deliberate operator choice, independent of the incident's
|
|
18
|
+
severity. A new service-typed incident RPC `getActiveHealthOverrides` exposes
|
|
19
|
+
active overrides per system, which `@checkstack/healthcheck-backend` reads and
|
|
20
|
+
folds into `getSystemHealthStatus`. The system-health response gains an optional
|
|
21
|
+
`override` field naming the contributing incident so UIs can explain why a
|
|
22
|
+
system reads unhealthy when its checks look fine. The system health badge uses
|
|
23
|
+
it to show, on hover, when a status was forced by an incident.
|
|
24
|
+
|
|
25
|
+
The dashboard "problem system" signal attributes an override-forced status to
|
|
26
|
+
the incident ("Forced by incident: <title>") instead of misreporting
|
|
27
|
+
"0 of N checks failing", while a genuinely worse health check still drives the
|
|
28
|
+
signal and its detail. Public status pages reflect the forced status but never
|
|
29
|
+
carry the incident title (the widget DTOs project only the status), so an
|
|
30
|
+
override cannot leak the name of a hidden incident.
|
|
31
|
+
|
|
32
|
+
Behavior change: a system's derived health now reflects active incident
|
|
33
|
+
overrides in addition to its health checks. Adds a forward-only migration for
|
|
34
|
+
the new nullable `incidents.health_override` column.
|
|
35
|
+
|
|
36
|
+
Thanks to [@stuajnht](https://github.com/stuajnht) for the valuable feedback
|
|
37
|
+
that shaped this release.
|
|
38
|
+
|
|
3
39
|
## 1.7.2
|
|
4
40
|
|
|
5
41
|
### Patch Changes
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -6,11 +6,15 @@ export {
|
|
|
6
6
|
export {
|
|
7
7
|
incidentContract,
|
|
8
8
|
IncidentApi,
|
|
9
|
+
SystemHealthOverrideSchema,
|
|
10
|
+
type SystemHealthOverride,
|
|
9
11
|
type IncidentContract,
|
|
10
12
|
} from "./rpc-contract";
|
|
11
13
|
export {
|
|
12
14
|
IncidentStatusEnum,
|
|
13
15
|
IncidentSeverityEnum,
|
|
16
|
+
IncidentHealthOverrideEnum,
|
|
17
|
+
type IncidentHealthOverride,
|
|
14
18
|
IncidentSchema,
|
|
15
19
|
IncidentWithSystemsSchema,
|
|
16
20
|
IncidentUpdateSchema,
|
package/src/rpc-contract.ts
CHANGED
|
@@ -12,11 +12,24 @@ import {
|
|
|
12
12
|
UpdateIncidentInputSchema,
|
|
13
13
|
AddIncidentUpdateInputSchema,
|
|
14
14
|
IncidentStatusEnum,
|
|
15
|
+
IncidentHealthOverrideEnum,
|
|
15
16
|
BulkIncidentActionResultSchema,
|
|
16
17
|
BulkIncidentIdsInputSchema,
|
|
17
18
|
BulkResolveIncidentsInputSchema,
|
|
18
19
|
} from "./schemas";
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* One active incident forcing a health status onto a system. Returned by
|
|
23
|
+
* `getActiveHealthOverrides` and consumed by `@checkstack/healthcheck-backend`
|
|
24
|
+
* to fold incident overrides into a system's derived health (worst-wins).
|
|
25
|
+
*/
|
|
26
|
+
export const SystemHealthOverrideSchema = z.object({
|
|
27
|
+
status: IncidentHealthOverrideEnum,
|
|
28
|
+
incidentId: z.string(),
|
|
29
|
+
incidentTitle: z.string(),
|
|
30
|
+
});
|
|
31
|
+
export type SystemHealthOverride = z.infer<typeof SystemHealthOverrideSchema>;
|
|
32
|
+
|
|
20
33
|
export const incidentContract = {
|
|
21
34
|
/** List all incidents with optional filters */
|
|
22
35
|
listIncidents: proc({
|
|
@@ -243,6 +256,27 @@ export const incidentContract = {
|
|
|
243
256
|
.input(z.object({ systemId: z.string() }))
|
|
244
257
|
.output(z.object({ suppressed: z.boolean() })),
|
|
245
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Return, for each requested system, the health overrides contributed by its
|
|
261
|
+
* currently ACTIVE incidents (status != resolved, `healthOverride` set).
|
|
262
|
+
* Server-to-server read used by `@checkstack/healthcheck-backend` to fold
|
|
263
|
+
* incident overrides into a system's derived health via worst-wins. Systems
|
|
264
|
+
* with no active override are omitted from the record. Resolved incidents
|
|
265
|
+
* never appear, so an override auto-lifts the moment its incident resolves.
|
|
266
|
+
*/
|
|
267
|
+
getActiveHealthOverrides: proc({
|
|
268
|
+
operationType: "query",
|
|
269
|
+
userType: "service",
|
|
270
|
+
access: [incidentAccess.incident.read],
|
|
271
|
+
})
|
|
272
|
+
.route({ method: "POST" })
|
|
273
|
+
.input(z.object({ systemIds: z.array(z.string()) }))
|
|
274
|
+
.output(
|
|
275
|
+
z.object({
|
|
276
|
+
overrides: z.record(z.string(), z.array(SystemHealthOverrideSchema)),
|
|
277
|
+
}),
|
|
278
|
+
),
|
|
279
|
+
|
|
246
280
|
/**
|
|
247
281
|
* Open an incident on behalf of another plugin (no user context).
|
|
248
282
|
* Used by automated systems like the health-check auto-incident flow.
|
package/src/schemas.ts
CHANGED
|
@@ -20,6 +20,22 @@ export type IncidentStatus = z.infer<typeof IncidentStatusEnum>;
|
|
|
20
20
|
export const IncidentSeverityEnum = z.enum(["minor", "major", "critical"]);
|
|
21
21
|
export type IncidentSeverity = z.infer<typeof IncidentSeverityEnum>;
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Optional health status an incident forces onto its affected systems while the
|
|
25
|
+
* incident is active (not resolved). It is a DELIBERATE user choice, independent
|
|
26
|
+
* of the incident's `severity` - the operator picks how the components should
|
|
27
|
+
* read on every health surface, not a value derived from impact ranking.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately a subset of the health-check status vocabulary
|
|
30
|
+
* (`healthy | degraded | unhealthy`): forcing a system to `healthy` via an
|
|
31
|
+
* incident makes no sense, so only the two "problem" states are offered. The
|
|
32
|
+
* override folds into the system's health via worst-wins alongside the
|
|
33
|
+
* automated checks (see `@checkstack/healthcheck-backend`), so a check reporting
|
|
34
|
+
* something worse always wins.
|
|
35
|
+
*/
|
|
36
|
+
export const IncidentHealthOverrideEnum = z.enum(["degraded", "unhealthy"]);
|
|
37
|
+
export type IncidentHealthOverride = z.infer<typeof IncidentHealthOverrideEnum>;
|
|
38
|
+
|
|
23
39
|
/**
|
|
24
40
|
* Core incident entity schema
|
|
25
41
|
*/
|
|
@@ -30,6 +46,12 @@ export const IncidentSchema = z.object({
|
|
|
30
46
|
status: IncidentStatusEnum,
|
|
31
47
|
severity: IncidentSeverityEnum,
|
|
32
48
|
suppressNotifications: z.boolean(),
|
|
49
|
+
/**
|
|
50
|
+
* Optional health status forced onto the incident's affected systems while it
|
|
51
|
+
* is active. `null` (the default) means the incident does not touch derived
|
|
52
|
+
* system health. See {@link IncidentHealthOverrideEnum}.
|
|
53
|
+
*/
|
|
54
|
+
healthOverride: IncidentHealthOverrideEnum.nullable(),
|
|
33
55
|
createdAt: z.date(),
|
|
34
56
|
updatedAt: z.date(),
|
|
35
57
|
});
|
|
@@ -91,6 +113,11 @@ export const CreateIncidentInputSchema = z.object({
|
|
|
91
113
|
description: z.string().optional(),
|
|
92
114
|
severity: IncidentSeverityEnum,
|
|
93
115
|
suppressNotifications: z.boolean().optional().default(false),
|
|
116
|
+
/**
|
|
117
|
+
* Optional health status to force onto the affected systems while the incident
|
|
118
|
+
* is active. Omit or pass `null` to leave derived system health untouched.
|
|
119
|
+
*/
|
|
120
|
+
healthOverride: IncidentHealthOverrideEnum.nullable().optional(),
|
|
94
121
|
systemIds: z.array(z.string()).min(1, "At least one system is required"),
|
|
95
122
|
initialMessage: z
|
|
96
123
|
.string()
|
|
@@ -114,6 +141,12 @@ export const UpdateIncidentInputSchema = z.object({
|
|
|
114
141
|
description: z.string().nullable().optional(),
|
|
115
142
|
severity: IncidentSeverityEnum.optional(),
|
|
116
143
|
suppressNotifications: z.boolean().optional(),
|
|
144
|
+
/**
|
|
145
|
+
* Set to a status to force it, or `null` to clear a previously-set override.
|
|
146
|
+
* Omit to leave the current override unchanged (same nullable-optional
|
|
147
|
+
* clearing semantics as `description`).
|
|
148
|
+
*/
|
|
149
|
+
healthOverride: IncidentHealthOverrideEnum.nullable().optional(),
|
|
117
150
|
systemIds: z.array(z.string()).min(1).optional(),
|
|
118
151
|
});
|
|
119
152
|
export type UpdateIncidentInput = z.infer<typeof UpdateIncidentInputSchema>;
|
package/src/signals.test.ts
CHANGED
|
@@ -15,6 +15,7 @@ const baseIncident = (
|
|
|
15
15
|
status: "investigating",
|
|
16
16
|
severity: "critical",
|
|
17
17
|
suppressNotifications: false,
|
|
18
|
+
healthOverride: null,
|
|
18
19
|
createdAt: new Date("2026-06-01T10:00:00.000Z"),
|
|
19
20
|
updatedAt: new Date("2026-06-01T10:00:00.000Z"),
|
|
20
21
|
systemIds: ["sys-1"],
|