@checkstack/healthcheck-script-backend 0.3.9 → 0.3.10
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 +20 -0
- package/package.json +3 -3
- package/src/execute-collector.test.ts +2 -2
- package/src/execute-collector.ts +16 -0
- package/src/inline-script-collector.ts +29 -6
- package/src/strategy.test.ts +7 -7
- package/src/strategy.ts +21 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @checkstack/healthcheck-script-backend
|
|
2
2
|
|
|
3
|
+
## 0.3.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 8d1ef12: ## Downstream consumer bumps for the anomaly detection + cache system rollout
|
|
8
|
+
|
|
9
|
+
Packages on this branch were updated as part of the anomaly detection feature (schema annotations on result fields, plugin metadata for the modular cache system) but were not listed in the upstream changesets.
|
|
10
|
+
|
|
11
|
+
- **`@checkstack/healthcheck-common`** (minor) — new RPC contract additions and schema changes supporting per-field anomaly metadata.
|
|
12
|
+
- **`@checkstack/cache-memory-common`** (minor) — new package providing access rules + plugin metadata for the in-memory cache backend.
|
|
13
|
+
- **healthcheck plugins** (patch) — adopt the new `x-anomaly-*` schema annotations on their result fields so anomaly detection works automatically against their checks. No public API changes.
|
|
14
|
+
- **integration / notification / auth / queue / collector plugins** (patch) — minor internal updates as consumers of upstream API changes (cache plugin registry, schema additions). No public API changes.
|
|
15
|
+
|
|
16
|
+
- Updated dependencies [8d1ef12]
|
|
17
|
+
- Updated dependencies [8d1ef12]
|
|
18
|
+
- Updated dependencies [8d1ef12]
|
|
19
|
+
- @checkstack/healthcheck-common@0.12.0
|
|
20
|
+
- @checkstack/common@0.7.0
|
|
21
|
+
- @checkstack/backend-api@0.13.0
|
|
22
|
+
|
|
3
23
|
## 0.3.9
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@checkstack/healthcheck-script-backend",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"checkstack": {
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"lint:code": "eslint . --max-warnings 0"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@checkstack/backend-api": "0.
|
|
15
|
+
"@checkstack/backend-api": "0.12.0",
|
|
16
16
|
"@checkstack/common": "0.6.5",
|
|
17
|
-
"@checkstack/healthcheck-common": "0.
|
|
17
|
+
"@checkstack/healthcheck-common": "0.11.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
20
|
"@types/bun": "^1.0.0",
|
|
@@ -10,7 +10,7 @@ describe("ExecuteCollector", () => {
|
|
|
10
10
|
stderr?: string;
|
|
11
11
|
timedOut?: boolean;
|
|
12
12
|
error?: string;
|
|
13
|
-
} = {}
|
|
13
|
+
} = {},
|
|
14
14
|
): ScriptTransportClient => ({
|
|
15
15
|
exec: mock(() =>
|
|
16
16
|
Promise.resolve({
|
|
@@ -19,7 +19,7 @@ describe("ExecuteCollector", () => {
|
|
|
19
19
|
stderr: response.stderr ?? "",
|
|
20
20
|
timedOut: response.timedOut ?? false,
|
|
21
21
|
error: response.error,
|
|
22
|
-
})
|
|
22
|
+
}),
|
|
23
23
|
),
|
|
24
24
|
});
|
|
25
25
|
|
package/src/execute-collector.ts
CHANGED
|
@@ -52,27 +52,39 @@ const executeResultSchema = healthResultSchema({
|
|
|
52
52
|
exitCode: healthResultNumber({
|
|
53
53
|
"x-chart-type": "counter",
|
|
54
54
|
"x-chart-label": "Exit Code",
|
|
55
|
+
"x-anomaly-enabled": true,
|
|
56
|
+
"x-anomaly-direction": "dominance",
|
|
55
57
|
}),
|
|
56
58
|
stdout: healthResultString({
|
|
57
59
|
"x-chart-type": "text",
|
|
58
60
|
"x-chart-label": "Standard Output",
|
|
61
|
+
"x-anomaly-enabled": false,
|
|
59
62
|
}),
|
|
60
63
|
stderr: healthResultString({
|
|
61
64
|
"x-chart-type": "text",
|
|
62
65
|
"x-chart-label": "Standard Error",
|
|
66
|
+
"x-anomaly-enabled": false,
|
|
63
67
|
}),
|
|
64
68
|
executionTimeMs: healthResultNumber({
|
|
65
69
|
"x-chart-type": "line",
|
|
66
70
|
"x-chart-label": "Execution Time",
|
|
67
71
|
"x-chart-unit": "ms",
|
|
72
|
+
"x-anomaly-enabled": true,
|
|
73
|
+
"x-anomaly-direction": "lower-is-better",
|
|
74
|
+
"x-anomaly-sensitivity": 2,
|
|
75
|
+
"x-anomaly-confirmation-window": 3,
|
|
68
76
|
}),
|
|
69
77
|
success: healthResultBoolean({
|
|
70
78
|
"x-chart-type": "boolean",
|
|
71
79
|
"x-chart-label": "Success",
|
|
80
|
+
"x-anomaly-enabled": true,
|
|
81
|
+
"x-anomaly-direction": "dominance",
|
|
72
82
|
}),
|
|
73
83
|
timedOut: healthResultBoolean({
|
|
74
84
|
"x-chart-type": "boolean",
|
|
75
85
|
"x-chart-label": "Timed Out",
|
|
86
|
+
"x-anomaly-enabled": true,
|
|
87
|
+
"x-anomaly-direction": "dominance",
|
|
76
88
|
}),
|
|
77
89
|
});
|
|
78
90
|
|
|
@@ -84,11 +96,15 @@ const executeAggregatedFields = {
|
|
|
84
96
|
"x-chart-type": "line",
|
|
85
97
|
"x-chart-label": "Avg Execution Time",
|
|
86
98
|
"x-chart-unit": "ms",
|
|
99
|
+
"x-anomaly-enabled": true,
|
|
100
|
+
"x-anomaly-direction": "lower-is-better",
|
|
87
101
|
}),
|
|
88
102
|
successRate: aggregatedRate({
|
|
89
103
|
"x-chart-type": "gauge",
|
|
90
104
|
"x-chart-label": "Success Rate",
|
|
91
105
|
"x-chart-unit": "%",
|
|
106
|
+
"x-anomaly-enabled": true,
|
|
107
|
+
"x-anomaly-direction": "higher-is-better",
|
|
92
108
|
}),
|
|
93
109
|
};
|
|
94
110
|
|
|
@@ -106,10 +106,14 @@ async function executeInlineScript({
|
|
|
106
106
|
const worker = new Worker(workerUrl);
|
|
107
107
|
|
|
108
108
|
const workerResult = await Promise.race([
|
|
109
|
-
new Promise<{ result?: unknown; error?: string; logs: string[] }>(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
new Promise<{ result?: unknown; error?: string; logs: string[] }>(
|
|
110
|
+
(resolve) => {
|
|
111
|
+
worker.addEventListener("message", (event: MessageEvent) =>
|
|
112
|
+
resolve(event.data),
|
|
113
|
+
);
|
|
114
|
+
worker.postMessage({ script, config: context.config });
|
|
115
|
+
},
|
|
116
|
+
),
|
|
113
117
|
new Promise<never>((_, reject) =>
|
|
114
118
|
setTimeout(() => reject(new Error("__TIMEOUT__")), timeoutMs),
|
|
115
119
|
),
|
|
@@ -153,8 +157,12 @@ async function executeInlineScript({
|
|
|
153
157
|
return {
|
|
154
158
|
result: {
|
|
155
159
|
success: Boolean(resultObj.success ?? true),
|
|
156
|
-
message:
|
|
157
|
-
|
|
160
|
+
message:
|
|
161
|
+
typeof resultObj.message === "string"
|
|
162
|
+
? resultObj.message
|
|
163
|
+
: undefined,
|
|
164
|
+
value:
|
|
165
|
+
typeof resultObj.value === "number" ? resultObj.value : undefined,
|
|
158
166
|
},
|
|
159
167
|
timedOut: false,
|
|
160
168
|
};
|
|
@@ -204,23 +212,34 @@ const inlineScriptResultSchema = healthResultSchema({
|
|
|
204
212
|
success: healthResultBoolean({
|
|
205
213
|
"x-chart-type": "boolean",
|
|
206
214
|
"x-chart-label": "Success",
|
|
215
|
+
"x-anomaly-enabled": true,
|
|
216
|
+
"x-anomaly-direction": "dominance",
|
|
207
217
|
}),
|
|
208
218
|
message: healthResultString({
|
|
209
219
|
"x-chart-type": "text",
|
|
210
220
|
"x-chart-label": "Message",
|
|
221
|
+
"x-anomaly-enabled": false,
|
|
211
222
|
}).optional(),
|
|
212
223
|
value: healthResultNumber({
|
|
213
224
|
"x-chart-type": "line",
|
|
214
225
|
"x-chart-label": "Value",
|
|
226
|
+
"x-anomaly-enabled": true,
|
|
227
|
+
"x-anomaly-direction": "deviation",
|
|
215
228
|
}).optional(),
|
|
216
229
|
executionTimeMs: healthResultNumber({
|
|
217
230
|
"x-chart-type": "line",
|
|
218
231
|
"x-chart-label": "Execution Time",
|
|
219
232
|
"x-chart-unit": "ms",
|
|
233
|
+
"x-anomaly-enabled": true,
|
|
234
|
+
"x-anomaly-direction": "lower-is-better",
|
|
235
|
+
"x-anomaly-sensitivity": 2,
|
|
236
|
+
"x-anomaly-confirmation-window": 3,
|
|
220
237
|
}),
|
|
221
238
|
timedOut: healthResultBoolean({
|
|
222
239
|
"x-chart-type": "boolean",
|
|
223
240
|
"x-chart-label": "Timed Out",
|
|
241
|
+
"x-anomaly-enabled": true,
|
|
242
|
+
"x-anomaly-direction": "dominance",
|
|
224
243
|
}),
|
|
225
244
|
});
|
|
226
245
|
|
|
@@ -232,11 +251,15 @@ const inlineScriptAggregatedFields = {
|
|
|
232
251
|
"x-chart-type": "line",
|
|
233
252
|
"x-chart-label": "Avg Execution Time",
|
|
234
253
|
"x-chart-unit": "ms",
|
|
254
|
+
"x-anomaly-enabled": true,
|
|
255
|
+
"x-anomaly-direction": "lower-is-better",
|
|
235
256
|
}),
|
|
236
257
|
successRate: aggregatedRate({
|
|
237
258
|
"x-chart-type": "gauge",
|
|
238
259
|
"x-chart-label": "Success Rate",
|
|
239
260
|
"x-chart-unit": "%",
|
|
261
|
+
"x-anomaly-enabled": true,
|
|
262
|
+
"x-anomaly-direction": "higher-is-better",
|
|
240
263
|
}),
|
|
241
264
|
};
|
|
242
265
|
|
package/src/strategy.test.ts
CHANGED
|
@@ -10,7 +10,7 @@ describe("ScriptHealthCheckStrategy", () => {
|
|
|
10
10
|
stderr?: string;
|
|
11
11
|
timedOut?: boolean;
|
|
12
12
|
error?: Error;
|
|
13
|
-
} = {}
|
|
13
|
+
} = {},
|
|
14
14
|
): ScriptExecutor => ({
|
|
15
15
|
execute: mock(() =>
|
|
16
16
|
config.error
|
|
@@ -20,7 +20,7 @@ describe("ScriptHealthCheckStrategy", () => {
|
|
|
20
20
|
stdout: config.stdout ?? "",
|
|
21
21
|
stderr: config.stderr ?? "",
|
|
22
22
|
timedOut: config.timedOut ?? false,
|
|
23
|
-
})
|
|
23
|
+
}),
|
|
24
24
|
),
|
|
25
25
|
});
|
|
26
26
|
|
|
@@ -45,7 +45,7 @@ describe("ScriptHealthCheckStrategy", () => {
|
|
|
45
45
|
describe("client.exec", () => {
|
|
46
46
|
it("should return successful result for successful script execution", async () => {
|
|
47
47
|
const strategy = new ScriptHealthCheckStrategy(
|
|
48
|
-
createMockExecutor({ exitCode: 0, stdout: "OK" })
|
|
48
|
+
createMockExecutor({ exitCode: 0, stdout: "OK" }),
|
|
49
49
|
);
|
|
50
50
|
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
51
51
|
|
|
@@ -63,7 +63,7 @@ describe("ScriptHealthCheckStrategy", () => {
|
|
|
63
63
|
|
|
64
64
|
it("should return non-zero exit code for failed script", async () => {
|
|
65
65
|
const strategy = new ScriptHealthCheckStrategy(
|
|
66
|
-
createMockExecutor({ exitCode: 1, stderr: "Error" })
|
|
66
|
+
createMockExecutor({ exitCode: 1, stderr: "Error" }),
|
|
67
67
|
);
|
|
68
68
|
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
69
69
|
|
|
@@ -80,7 +80,7 @@ describe("ScriptHealthCheckStrategy", () => {
|
|
|
80
80
|
|
|
81
81
|
it("should indicate timeout for timed out script", async () => {
|
|
82
82
|
const strategy = new ScriptHealthCheckStrategy(
|
|
83
|
-
createMockExecutor({ timedOut: true, exitCode: -1 })
|
|
83
|
+
createMockExecutor({ timedOut: true, exitCode: -1 }),
|
|
84
84
|
);
|
|
85
85
|
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
86
86
|
|
|
@@ -97,7 +97,7 @@ describe("ScriptHealthCheckStrategy", () => {
|
|
|
97
97
|
|
|
98
98
|
it("should return error for execution error", async () => {
|
|
99
99
|
const strategy = new ScriptHealthCheckStrategy(
|
|
100
|
-
createMockExecutor({ error: new Error("Command not found") })
|
|
100
|
+
createMockExecutor({ error: new Error("Command not found") }),
|
|
101
101
|
);
|
|
102
102
|
const connectedClient = await strategy.createClient({ timeout: 5000 });
|
|
103
103
|
|
|
@@ -131,7 +131,7 @@ describe("ScriptHealthCheckStrategy", () => {
|
|
|
131
131
|
args: ["--verbose", "--env=prod"],
|
|
132
132
|
cwd: "/opt/scripts",
|
|
133
133
|
env: { API_KEY: "secret" },
|
|
134
|
-
})
|
|
134
|
+
}),
|
|
135
135
|
);
|
|
136
136
|
|
|
137
137
|
connectedClient.close();
|
package/src/strategy.ts
CHANGED
|
@@ -58,27 +58,40 @@ const scriptResultSchema = healthResultSchema({
|
|
|
58
58
|
executed: healthResultBoolean({
|
|
59
59
|
"x-chart-type": "boolean",
|
|
60
60
|
"x-chart-label": "Executed",
|
|
61
|
+
"x-anomaly-enabled": true,
|
|
62
|
+
"x-anomaly-direction": "dominance",
|
|
61
63
|
}),
|
|
62
64
|
executionTimeMs: healthResultNumber({
|
|
63
65
|
"x-chart-type": "line",
|
|
64
66
|
"x-chart-label": "Execution Time",
|
|
65
67
|
"x-chart-unit": "ms",
|
|
68
|
+
"x-anomaly-enabled": true,
|
|
69
|
+
"x-anomaly-direction": "lower-is-better",
|
|
70
|
+
"x-anomaly-sensitivity": 2,
|
|
71
|
+
"x-anomaly-confirmation-window": 3,
|
|
66
72
|
}),
|
|
67
73
|
exitCode: healthResultNumber({
|
|
68
74
|
"x-chart-type": "counter",
|
|
69
75
|
"x-chart-label": "Exit Code",
|
|
76
|
+
"x-anomaly-enabled": true,
|
|
77
|
+
"x-anomaly-direction": "dominance",
|
|
70
78
|
}).optional(),
|
|
71
79
|
success: healthResultBoolean({
|
|
72
80
|
"x-chart-type": "boolean",
|
|
73
81
|
"x-chart-label": "Success",
|
|
82
|
+
"x-anomaly-enabled": true,
|
|
83
|
+
"x-anomaly-direction": "dominance",
|
|
74
84
|
}),
|
|
75
85
|
timedOut: healthResultBoolean({
|
|
76
86
|
"x-chart-type": "boolean",
|
|
77
87
|
"x-chart-label": "Timed Out",
|
|
88
|
+
"x-anomaly-enabled": true,
|
|
89
|
+
"x-anomaly-direction": "dominance",
|
|
78
90
|
}),
|
|
79
91
|
error: healthResultString({
|
|
80
92
|
"x-chart-type": "status",
|
|
81
93
|
"x-chart-label": "Error",
|
|
94
|
+
"x-anomaly-enabled": false,
|
|
82
95
|
}).optional(),
|
|
83
96
|
});
|
|
84
97
|
|
|
@@ -90,19 +103,27 @@ const scriptAggregatedFields = {
|
|
|
90
103
|
"x-chart-type": "line",
|
|
91
104
|
"x-chart-label": "Avg Execution Time",
|
|
92
105
|
"x-chart-unit": "ms",
|
|
106
|
+
"x-anomaly-enabled": true,
|
|
107
|
+
"x-anomaly-direction": "lower-is-better",
|
|
93
108
|
}),
|
|
94
109
|
successRate: aggregatedRate({
|
|
95
110
|
"x-chart-type": "gauge",
|
|
96
111
|
"x-chart-label": "Success Rate",
|
|
97
112
|
"x-chart-unit": "%",
|
|
113
|
+
"x-anomaly-enabled": true,
|
|
114
|
+
"x-anomaly-direction": "higher-is-better",
|
|
98
115
|
}),
|
|
99
116
|
errorCount: aggregatedCounter({
|
|
100
117
|
"x-chart-type": "counter",
|
|
101
118
|
"x-chart-label": "Errors",
|
|
119
|
+
"x-anomaly-enabled": true,
|
|
120
|
+
"x-anomaly-direction": "lower-is-better",
|
|
102
121
|
}),
|
|
103
122
|
timeoutCount: aggregatedCounter({
|
|
104
123
|
"x-chart-type": "counter",
|
|
105
124
|
"x-chart-label": "Timeouts",
|
|
125
|
+
"x-anomaly-enabled": true,
|
|
126
|
+
"x-anomaly-direction": "lower-is-better",
|
|
106
127
|
}),
|
|
107
128
|
};
|
|
108
129
|
|