tlc-claude-code 1.4.8 → 1.4.9
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/package.json +1 -1
- package/server/index.js +229 -14
- package/server/lib/compliance/control-mapper.js +401 -0
- package/server/lib/compliance/control-mapper.test.js +117 -0
- package/server/lib/compliance/evidence-linker.js +296 -0
- package/server/lib/compliance/evidence-linker.test.js +121 -0
- package/server/lib/compliance/gdpr-checklist.js +416 -0
- package/server/lib/compliance/gdpr-checklist.test.js +131 -0
- package/server/lib/compliance/hipaa-checklist.js +277 -0
- package/server/lib/compliance/hipaa-checklist.test.js +101 -0
- package/server/lib/compliance/iso27001-checklist.js +287 -0
- package/server/lib/compliance/iso27001-checklist.test.js +99 -0
- package/server/lib/compliance/multi-framework-reporter.js +284 -0
- package/server/lib/compliance/multi-framework-reporter.test.js +127 -0
- package/server/lib/compliance/pci-dss-checklist.js +214 -0
- package/server/lib/compliance/pci-dss-checklist.test.js +95 -0
- package/server/lib/compliance/trust-centre.js +187 -0
- package/server/lib/compliance/trust-centre.test.js +93 -0
- package/server/lib/dashboard/api-server.js +155 -0
- package/server/lib/dashboard/api-server.test.js +155 -0
- package/server/lib/dashboard/health-api.js +199 -0
- package/server/lib/dashboard/health-api.test.js +122 -0
- package/server/lib/dashboard/notes-api.js +234 -0
- package/server/lib/dashboard/notes-api.test.js +134 -0
- package/server/lib/dashboard/router-api.js +176 -0
- package/server/lib/dashboard/router-api.test.js +132 -0
- package/server/lib/dashboard/tasks-api.js +289 -0
- package/server/lib/dashboard/tasks-api.test.js +161 -0
- package/server/lib/dashboard/tlc-introspection.js +197 -0
- package/server/lib/dashboard/tlc-introspection.test.js +138 -0
- package/server/lib/dashboard/version-api.js +222 -0
- package/server/lib/dashboard/version-api.test.js +112 -0
- package/server/lib/dashboard/websocket-server.js +104 -0
- package/server/lib/dashboard/websocket-server.test.js +118 -0
- package/server/lib/deploy/branch-classifier.js +163 -0
- package/server/lib/deploy/branch-classifier.test.js +164 -0
- package/server/lib/deploy/deployment-approval.js +299 -0
- package/server/lib/deploy/deployment-approval.test.js +296 -0
- package/server/lib/deploy/deployment-audit.js +374 -0
- package/server/lib/deploy/deployment-audit.test.js +307 -0
- package/server/lib/deploy/deployment-executor.js +335 -0
- package/server/lib/deploy/deployment-executor.test.js +329 -0
- package/server/lib/deploy/deployment-rules.js +163 -0
- package/server/lib/deploy/deployment-rules.test.js +188 -0
- package/server/lib/deploy/rollback-manager.js +379 -0
- package/server/lib/deploy/rollback-manager.test.js +321 -0
- package/server/lib/deploy/security-gates.js +236 -0
- package/server/lib/deploy/security-gates.test.js +222 -0
- package/server/lib/k8s/gitops-config.js +188 -0
- package/server/lib/k8s/gitops-config.test.js +59 -0
- package/server/lib/k8s/helm-generator.js +196 -0
- package/server/lib/k8s/helm-generator.test.js +59 -0
- package/server/lib/k8s/kustomize-generator.js +176 -0
- package/server/lib/k8s/kustomize-generator.test.js +58 -0
- package/server/lib/k8s/network-policy.js +114 -0
- package/server/lib/k8s/network-policy.test.js +53 -0
- package/server/lib/k8s/pod-security.js +114 -0
- package/server/lib/k8s/pod-security.test.js +55 -0
- package/server/lib/k8s/rbac-generator.js +132 -0
- package/server/lib/k8s/rbac-generator.test.js +57 -0
- package/server/lib/k8s/resource-manager.js +172 -0
- package/server/lib/k8s/resource-manager.test.js +60 -0
- package/server/lib/k8s/secrets-encryption.js +168 -0
- package/server/lib/k8s/secrets-encryption.test.js +49 -0
- package/server/lib/monitoring/alert-manager.js +238 -0
- package/server/lib/monitoring/alert-manager.test.js +106 -0
- package/server/lib/monitoring/health-check.js +226 -0
- package/server/lib/monitoring/health-check.test.js +176 -0
- package/server/lib/monitoring/incident-manager.js +230 -0
- package/server/lib/monitoring/incident-manager.test.js +98 -0
- package/server/lib/monitoring/log-aggregator.js +147 -0
- package/server/lib/monitoring/log-aggregator.test.js +89 -0
- package/server/lib/monitoring/metrics-collector.js +337 -0
- package/server/lib/monitoring/metrics-collector.test.js +172 -0
- package/server/lib/monitoring/status-page.js +214 -0
- package/server/lib/monitoring/status-page.test.js +105 -0
- package/server/lib/monitoring/uptime-monitor.js +194 -0
- package/server/lib/monitoring/uptime-monitor.test.js +109 -0
- package/server/lib/network/fail2ban-config.js +294 -0
- package/server/lib/network/fail2ban-config.test.js +275 -0
- package/server/lib/network/firewall-manager.js +252 -0
- package/server/lib/network/firewall-manager.test.js +254 -0
- package/server/lib/network/geoip-filter.js +282 -0
- package/server/lib/network/geoip-filter.test.js +264 -0
- package/server/lib/network/rate-limiter.js +229 -0
- package/server/lib/network/rate-limiter.test.js +293 -0
- package/server/lib/network/request-validator.js +351 -0
- package/server/lib/network/request-validator.test.js +345 -0
- package/server/lib/network/security-headers.js +251 -0
- package/server/lib/network/security-headers.test.js +283 -0
- package/server/lib/network/tls-config.js +210 -0
- package/server/lib/network/tls-config.test.js +248 -0
- package/server/lib/security/auth-security.js +369 -0
- package/server/lib/security/auth-security.test.js +448 -0
- package/server/lib/security/cis-benchmark.js +152 -0
- package/server/lib/security/cis-benchmark.test.js +137 -0
- package/server/lib/security/compose-templates.js +312 -0
- package/server/lib/security/compose-templates.test.js +229 -0
- package/server/lib/security/container-runtime.js +456 -0
- package/server/lib/security/container-runtime.test.js +503 -0
- package/server/lib/security/cors-validator.js +278 -0
- package/server/lib/security/cors-validator.test.js +310 -0
- package/server/lib/security/crypto-utils.js +253 -0
- package/server/lib/security/crypto-utils.test.js +409 -0
- package/server/lib/security/dockerfile-linter.js +459 -0
- package/server/lib/security/dockerfile-linter.test.js +483 -0
- package/server/lib/security/dockerfile-templates.js +278 -0
- package/server/lib/security/dockerfile-templates.test.js +164 -0
- package/server/lib/security/error-sanitizer.js +426 -0
- package/server/lib/security/error-sanitizer.test.js +331 -0
- package/server/lib/security/headers-generator.js +368 -0
- package/server/lib/security/headers-generator.test.js +398 -0
- package/server/lib/security/image-scanner.js +83 -0
- package/server/lib/security/image-scanner.test.js +106 -0
- package/server/lib/security/input-validator.js +352 -0
- package/server/lib/security/input-validator.test.js +330 -0
- package/server/lib/security/network-policy.js +174 -0
- package/server/lib/security/network-policy.test.js +164 -0
- package/server/lib/security/output-encoder.js +237 -0
- package/server/lib/security/output-encoder.test.js +276 -0
- package/server/lib/security/path-validator.js +359 -0
- package/server/lib/security/path-validator.test.js +293 -0
- package/server/lib/security/query-builder.js +421 -0
- package/server/lib/security/query-builder.test.js +318 -0
- package/server/lib/security/secret-detector.js +290 -0
- package/server/lib/security/secret-detector.test.js +354 -0
- package/server/lib/security/secrets-validator.js +137 -0
- package/server/lib/security/secrets-validator.test.js +120 -0
- package/server/lib/security-testing/dast-runner.js +154 -0
- package/server/lib/security-testing/dast-runner.test.js +62 -0
- package/server/lib/security-testing/dependency-scanner.js +172 -0
- package/server/lib/security-testing/dependency-scanner.test.js +64 -0
- package/server/lib/security-testing/pentest-runner.js +230 -0
- package/server/lib/security-testing/pentest-runner.test.js +60 -0
- package/server/lib/security-testing/sast-runner.js +136 -0
- package/server/lib/security-testing/sast-runner.test.js +62 -0
- package/server/lib/security-testing/secret-scanner.js +153 -0
- package/server/lib/security-testing/secret-scanner.test.js +66 -0
- package/server/lib/security-testing/security-gate.js +216 -0
- package/server/lib/security-testing/security-gate.test.js +115 -0
- package/server/lib/security-testing/security-reporter.js +303 -0
- package/server/lib/security-testing/security-reporter.test.js +114 -0
- package/server/lib/standards/audit-checker.js +546 -0
- package/server/lib/standards/audit-checker.test.js +415 -0
- package/server/lib/standards/cleanup-executor.js +452 -0
- package/server/lib/standards/cleanup-executor.test.js +293 -0
- package/server/lib/standards/refactor-stepper.js +425 -0
- package/server/lib/standards/refactor-stepper.test.js +298 -0
- package/server/lib/standards/standards-injector.js +167 -0
- package/server/lib/standards/standards-injector.test.js +232 -0
- package/server/lib/user-management.test.js +284 -0
- package/server/lib/vps/backup-manager.js +157 -0
- package/server/lib/vps/backup-manager.test.js +59 -0
- package/server/lib/vps/caddy-config.js +159 -0
- package/server/lib/vps/caddy-config.test.js +48 -0
- package/server/lib/vps/compose-orchestrator.js +219 -0
- package/server/lib/vps/compose-orchestrator.test.js +50 -0
- package/server/lib/vps/database-config.js +208 -0
- package/server/lib/vps/database-config.test.js +47 -0
- package/server/lib/vps/deploy-script.js +211 -0
- package/server/lib/vps/deploy-script.test.js +53 -0
- package/server/lib/vps/secrets-manager.js +148 -0
- package/server/lib/vps/secrets-manager.test.js +58 -0
- package/server/lib/vps/server-hardening.js +174 -0
- package/server/lib/vps/server-hardening.test.js +70 -0
- package/server/package-lock.json +19 -0
- package/server/package.json +1 -0
- package/server/templates/CLAUDE.md +37 -0
- package/server/templates/CODING-STANDARDS.md +408 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Check Manager
|
|
3
|
+
* Liveness/readiness probes for container orchestration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const HEALTH_STATUS = {
|
|
7
|
+
HEALTHY: 'healthy',
|
|
8
|
+
UNHEALTHY: 'unhealthy',
|
|
9
|
+
DEGRADED: 'degraded',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a liveness probe that checks if the process is running
|
|
14
|
+
* @param {Object} options - Configuration options
|
|
15
|
+
* @param {boolean} options.includeMemory - Include memory usage in response
|
|
16
|
+
* @returns {Object} Liveness probe with check method
|
|
17
|
+
*/
|
|
18
|
+
export function createLivenessProbe(options = {}) {
|
|
19
|
+
const { includeMemory = false } = options;
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
async check() {
|
|
23
|
+
const result = {
|
|
24
|
+
status: HEALTH_STATUS.HEALTHY,
|
|
25
|
+
pid: process.pid,
|
|
26
|
+
uptime: process.uptime(),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
if (includeMemory) {
|
|
30
|
+
result.memory = process.memoryUsage();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return result;
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Create a readiness probe that checks dependencies
|
|
40
|
+
* @param {Object} options - Configuration options
|
|
41
|
+
* @param {Array} options.checks - Array of { name, check } objects
|
|
42
|
+
* @param {number} options.timeout - Timeout for each check in ms
|
|
43
|
+
* @returns {Object} Readiness probe with check method
|
|
44
|
+
*/
|
|
45
|
+
export function createReadinessProbe(options = {}) {
|
|
46
|
+
const { checks = [], timeout = 5000 } = options;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
async check() {
|
|
50
|
+
const failed = [];
|
|
51
|
+
const results = {};
|
|
52
|
+
|
|
53
|
+
for (const { name, check } of checks) {
|
|
54
|
+
try {
|
|
55
|
+
const checkPromise = check();
|
|
56
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
57
|
+
setTimeout(() => reject(new Error('timeout')), timeout)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const result = await Promise.race([checkPromise, timeoutPromise]);
|
|
61
|
+
results[name] = result;
|
|
62
|
+
|
|
63
|
+
if (!result) {
|
|
64
|
+
failed.push(name);
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
results[name] = false;
|
|
68
|
+
failed.push(name);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
status: failed.length === 0 ? HEALTH_STATUS.HEALTHY : HEALTH_STATUS.UNHEALTHY,
|
|
74
|
+
checks: results,
|
|
75
|
+
failed,
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create a deep health check for detailed verification
|
|
83
|
+
* @param {Object} options - Configuration options
|
|
84
|
+
* @param {Object} options.db - Database client
|
|
85
|
+
* @param {Object} options.cache - Cache client
|
|
86
|
+
* @param {Function} options.fetch - Fetch function for external services
|
|
87
|
+
* @returns {Object} Deep health check with various check methods
|
|
88
|
+
*/
|
|
89
|
+
export function createDeepHealthCheck(options = {}) {
|
|
90
|
+
const { db, cache, fetch: fetchFn } = options;
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
async checkDatabase() {
|
|
94
|
+
if (!db) {
|
|
95
|
+
return { healthy: false, error: 'No database configured' };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
await db.query('SELECT 1');
|
|
100
|
+
return { healthy: true };
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return { healthy: false, error: error.message };
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
|
|
106
|
+
async checkCache() {
|
|
107
|
+
if (!cache) {
|
|
108
|
+
return { healthy: false, error: 'No cache configured' };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const result = await cache.ping();
|
|
113
|
+
return { healthy: result === 'PONG' };
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return { healthy: false, error: error.message };
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
|
|
119
|
+
async checkExternal(url) {
|
|
120
|
+
if (!fetchFn) {
|
|
121
|
+
return { healthy: false, error: 'No fetch configured' };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
const response = await fetchFn(url);
|
|
126
|
+
return { healthy: response.ok };
|
|
127
|
+
} catch (error) {
|
|
128
|
+
return { healthy: false, error: error.message };
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
async getStatus() {
|
|
133
|
+
// Return status without sensitive info
|
|
134
|
+
return {
|
|
135
|
+
database: db ? 'configured' : 'not configured',
|
|
136
|
+
cache: cache ? 'configured' : 'not configured',
|
|
137
|
+
};
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Run all configured health checks
|
|
144
|
+
* @param {Object} options - Configuration options
|
|
145
|
+
* @param {boolean} options.liveness - Include liveness check
|
|
146
|
+
* @param {boolean} options.readiness - Include readiness check
|
|
147
|
+
* @param {Array} options.checks - Additional checks to run
|
|
148
|
+
* @returns {Object} Health check results
|
|
149
|
+
*/
|
|
150
|
+
export async function runHealthCheck(options = {}) {
|
|
151
|
+
const { liveness = false, readiness = false, checks = [] } = options;
|
|
152
|
+
const result = {};
|
|
153
|
+
let allHealthy = true;
|
|
154
|
+
|
|
155
|
+
if (liveness) {
|
|
156
|
+
const livenessProbe = createLivenessProbe();
|
|
157
|
+
result.liveness = await livenessProbe.check();
|
|
158
|
+
if (result.liveness.status !== HEALTH_STATUS.HEALTHY) {
|
|
159
|
+
allHealthy = false;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (readiness) {
|
|
164
|
+
const readinessProbe = createReadinessProbe({ checks });
|
|
165
|
+
result.readiness = await readinessProbe.check();
|
|
166
|
+
if (result.readiness.status !== HEALTH_STATUS.HEALTHY) {
|
|
167
|
+
allHealthy = false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Run individual checks if no liveness/readiness specified
|
|
172
|
+
if (!liveness && !readiness && checks.length > 0) {
|
|
173
|
+
for (const { name, check } of checks) {
|
|
174
|
+
try {
|
|
175
|
+
const checkResult = await check();
|
|
176
|
+
result[name] = checkResult;
|
|
177
|
+
if (!checkResult) {
|
|
178
|
+
allHealthy = false;
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
result[name] = false;
|
|
182
|
+
allHealthy = false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
result.overall = allHealthy ? HEALTH_STATUS.HEALTHY : HEALTH_STATUS.UNHEALTHY;
|
|
188
|
+
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Create a health check manager
|
|
194
|
+
* @param {Object} options - Configuration options
|
|
195
|
+
* @param {number} options.interval - Check interval in ms
|
|
196
|
+
* @returns {Object} Health check manager
|
|
197
|
+
*/
|
|
198
|
+
export function createHealthCheckManager(options = {}) {
|
|
199
|
+
const { interval = 30000 } = options;
|
|
200
|
+
const checks = [];
|
|
201
|
+
|
|
202
|
+
const livenessProbe = createLivenessProbe();
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
addCheck(check) {
|
|
206
|
+
checks.push(check);
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
async runAll() {
|
|
210
|
+
return runHealthCheck({ liveness: true, readiness: true, checks });
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
async getLiveness() {
|
|
214
|
+
return livenessProbe.check();
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
async getReadiness() {
|
|
218
|
+
const readinessProbe = createReadinessProbe({ checks });
|
|
219
|
+
return readinessProbe.check();
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
getInterval() {
|
|
223
|
+
return interval;
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health Check Manager Tests
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
5
|
+
import {
|
|
6
|
+
createLivenessProbe,
|
|
7
|
+
createReadinessProbe,
|
|
8
|
+
createDeepHealthCheck,
|
|
9
|
+
runHealthCheck,
|
|
10
|
+
HEALTH_STATUS,
|
|
11
|
+
createHealthCheckManager,
|
|
12
|
+
} from './health-check.js';
|
|
13
|
+
|
|
14
|
+
describe('health-check', () => {
|
|
15
|
+
describe('HEALTH_STATUS', () => {
|
|
16
|
+
it('defines status constants', () => {
|
|
17
|
+
expect(HEALTH_STATUS.HEALTHY).toBe('healthy');
|
|
18
|
+
expect(HEALTH_STATUS.UNHEALTHY).toBe('unhealthy');
|
|
19
|
+
expect(HEALTH_STATUS.DEGRADED).toBe('degraded');
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe('createLivenessProbe', () => {
|
|
24
|
+
it('returns healthy when process is running', async () => {
|
|
25
|
+
const probe = createLivenessProbe();
|
|
26
|
+
const result = await probe.check();
|
|
27
|
+
|
|
28
|
+
expect(result.status).toBe('healthy');
|
|
29
|
+
expect(result.pid).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('includes uptime', async () => {
|
|
33
|
+
const probe = createLivenessProbe();
|
|
34
|
+
const result = await probe.check();
|
|
35
|
+
|
|
36
|
+
expect(result.uptime).toBeDefined();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('includes memory usage', async () => {
|
|
40
|
+
const probe = createLivenessProbe({ includeMemory: true });
|
|
41
|
+
const result = await probe.check();
|
|
42
|
+
|
|
43
|
+
expect(result.memory).toBeDefined();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('createReadinessProbe', () => {
|
|
48
|
+
it('checks all dependencies', async () => {
|
|
49
|
+
const mockDbCheck = vi.fn().mockResolvedValue(true);
|
|
50
|
+
const mockCacheCheck = vi.fn().mockResolvedValue(true);
|
|
51
|
+
|
|
52
|
+
const probe = createReadinessProbe({
|
|
53
|
+
checks: [
|
|
54
|
+
{ name: 'database', check: mockDbCheck },
|
|
55
|
+
{ name: 'cache', check: mockCacheCheck },
|
|
56
|
+
],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const result = await probe.check();
|
|
60
|
+
|
|
61
|
+
expect(result.status).toBe('healthy');
|
|
62
|
+
expect(mockDbCheck).toHaveBeenCalled();
|
|
63
|
+
expect(mockCacheCheck).toHaveBeenCalled();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('returns unhealthy if any check fails', async () => {
|
|
67
|
+
const probe = createReadinessProbe({
|
|
68
|
+
checks: [
|
|
69
|
+
{ name: 'database', check: vi.fn().mockResolvedValue(true) },
|
|
70
|
+
{ name: 'cache', check: vi.fn().mockResolvedValue(false) },
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const result = await probe.check();
|
|
75
|
+
|
|
76
|
+
expect(result.status).toBe('unhealthy');
|
|
77
|
+
expect(result.failed).toContain('cache');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('handles check timeout', async () => {
|
|
81
|
+
const slowCheck = () => new Promise((r) => setTimeout(() => r(true), 5000));
|
|
82
|
+
|
|
83
|
+
const probe = createReadinessProbe({
|
|
84
|
+
checks: [{ name: 'slow', check: slowCheck }],
|
|
85
|
+
timeout: 100,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const result = await probe.check();
|
|
89
|
+
|
|
90
|
+
expect(result.status).toBe('unhealthy');
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe('createDeepHealthCheck', () => {
|
|
95
|
+
it('verifies database connection', async () => {
|
|
96
|
+
const mockDb = { query: vi.fn().mockResolvedValue([{ result: 1 }]) };
|
|
97
|
+
|
|
98
|
+
const check = createDeepHealthCheck({ db: mockDb });
|
|
99
|
+
const result = await check.checkDatabase();
|
|
100
|
+
|
|
101
|
+
expect(result.healthy).toBe(true);
|
|
102
|
+
expect(mockDb.query).toHaveBeenCalled();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('verifies cache connection', async () => {
|
|
106
|
+
const mockCache = { ping: vi.fn().mockResolvedValue('PONG') };
|
|
107
|
+
|
|
108
|
+
const check = createDeepHealthCheck({ cache: mockCache });
|
|
109
|
+
const result = await check.checkCache();
|
|
110
|
+
|
|
111
|
+
expect(result.healthy).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('verifies external service', async () => {
|
|
115
|
+
const mockFetch = vi.fn().mockResolvedValue({ ok: true });
|
|
116
|
+
|
|
117
|
+
const check = createDeepHealthCheck({ fetch: mockFetch });
|
|
118
|
+
const result = await check.checkExternal('https://api.example.com/health');
|
|
119
|
+
|
|
120
|
+
expect(result.healthy).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('does not leak sensitive info', async () => {
|
|
124
|
+
const check = createDeepHealthCheck({
|
|
125
|
+
db: { connectionString: 'postgres://user:pass@host/db' },
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const result = await check.getStatus();
|
|
129
|
+
|
|
130
|
+
expect(JSON.stringify(result)).not.toContain('pass');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('runHealthCheck', () => {
|
|
135
|
+
it('runs all configured checks', async () => {
|
|
136
|
+
const result = await runHealthCheck({
|
|
137
|
+
liveness: true,
|
|
138
|
+
readiness: true,
|
|
139
|
+
checks: [],
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
expect(result.liveness).toBeDefined();
|
|
143
|
+
expect(result.readiness).toBeDefined();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('aggregates status', async () => {
|
|
147
|
+
const result = await runHealthCheck({
|
|
148
|
+
checks: [
|
|
149
|
+
{ name: 'a', check: vi.fn().mockResolvedValue(true) },
|
|
150
|
+
{ name: 'b', check: vi.fn().mockResolvedValue(true) },
|
|
151
|
+
],
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
expect(result.overall).toBe('healthy');
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('createHealthCheckManager', () => {
|
|
159
|
+
it('creates manager with methods', () => {
|
|
160
|
+
const manager = createHealthCheckManager();
|
|
161
|
+
|
|
162
|
+
expect(manager.addCheck).toBeDefined();
|
|
163
|
+
expect(manager.runAll).toBeDefined();
|
|
164
|
+
expect(manager.getLiveness).toBeDefined();
|
|
165
|
+
expect(manager.getReadiness).toBeDefined();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('configures check intervals', () => {
|
|
169
|
+
const manager = createHealthCheckManager({
|
|
170
|
+
interval: 30000,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(manager.getInterval()).toBe(30000);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incident Manager
|
|
3
|
+
* Manages incidents with timeline tracking and MTTR metrics
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { randomUUID } from 'crypto';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Incident status constants
|
|
10
|
+
*/
|
|
11
|
+
export const INCIDENT_STATUS = {
|
|
12
|
+
OPEN: 'open',
|
|
13
|
+
INVESTIGATING: 'investigating',
|
|
14
|
+
RESOLVED: 'resolved',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new incident from an alert or manual trigger
|
|
19
|
+
* @param {Object} options - Incident options
|
|
20
|
+
* @param {Object} options.alert - Source alert
|
|
21
|
+
* @param {string} options.title - Incident title
|
|
22
|
+
* @returns {Object} Created incident
|
|
23
|
+
*/
|
|
24
|
+
export function createIncident(options = {}) {
|
|
25
|
+
const { alert, title } = options;
|
|
26
|
+
const now = new Date();
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
id: randomUUID(),
|
|
30
|
+
title: alert?.title || title || 'Untitled Incident',
|
|
31
|
+
status: INCIDENT_STATUS.OPEN,
|
|
32
|
+
alerts: alert ? [alert] : [],
|
|
33
|
+
createdAt: now,
|
|
34
|
+
updatedAt: now,
|
|
35
|
+
statusHistory: [
|
|
36
|
+
{
|
|
37
|
+
status: INCIDENT_STATUS.OPEN,
|
|
38
|
+
timestamp: now,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generates a timeline from incident events
|
|
46
|
+
* @param {Object} options - Timeline options
|
|
47
|
+
* @param {Array} options.events - Events to include
|
|
48
|
+
* @returns {Array} Sorted timeline entries
|
|
49
|
+
*/
|
|
50
|
+
export function generateTimeline(options) {
|
|
51
|
+
const { events } = options;
|
|
52
|
+
return events
|
|
53
|
+
.map(event => ({
|
|
54
|
+
...event,
|
|
55
|
+
timestamp: new Date(event.timestamp),
|
|
56
|
+
}))
|
|
57
|
+
.sort((a, b) => a.timestamp - b.timestamp);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Links related alerts to an incident
|
|
62
|
+
* @param {Object} incident - Incident to link to
|
|
63
|
+
* @param {Array} alerts - Alerts to link
|
|
64
|
+
* @returns {Object} Updated incident
|
|
65
|
+
*/
|
|
66
|
+
export function linkAlerts(incident, alerts) {
|
|
67
|
+
return {
|
|
68
|
+
...incident,
|
|
69
|
+
alerts: [...(incident.alerts || []), ...alerts],
|
|
70
|
+
updatedAt: new Date(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Updates incident status
|
|
76
|
+
* @param {Object} incident - Incident to update
|
|
77
|
+
* @param {string} status - New status
|
|
78
|
+
* @returns {Object} Updated incident
|
|
79
|
+
*/
|
|
80
|
+
export function updateStatus(incident, status) {
|
|
81
|
+
const now = new Date();
|
|
82
|
+
return {
|
|
83
|
+
...incident,
|
|
84
|
+
status,
|
|
85
|
+
updatedAt: now,
|
|
86
|
+
resolvedAt: status === INCIDENT_STATUS.RESOLVED ? now : incident.resolvedAt,
|
|
87
|
+
statusHistory: [
|
|
88
|
+
...(incident.statusHistory || []),
|
|
89
|
+
{
|
|
90
|
+
status,
|
|
91
|
+
timestamp: now,
|
|
92
|
+
},
|
|
93
|
+
],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Generates a post-mortem template for an incident
|
|
99
|
+
* @param {Object} options - Post-mortem options
|
|
100
|
+
* @param {Object} options.incident - Incident to generate post-mortem for
|
|
101
|
+
* @returns {string} Markdown post-mortem template
|
|
102
|
+
*/
|
|
103
|
+
export function generatePostMortem(options) {
|
|
104
|
+
const { incident } = options;
|
|
105
|
+
|
|
106
|
+
return `# Post-Mortem: ${incident.title}
|
|
107
|
+
|
|
108
|
+
## Incident ID
|
|
109
|
+
${incident.id}
|
|
110
|
+
|
|
111
|
+
## Summary
|
|
112
|
+
[Brief description of what happened]
|
|
113
|
+
|
|
114
|
+
## Timeline
|
|
115
|
+
[Key events and timestamps]
|
|
116
|
+
|
|
117
|
+
## Root Cause
|
|
118
|
+
[What caused the incident]
|
|
119
|
+
|
|
120
|
+
## Impact
|
|
121
|
+
[Who/what was affected]
|
|
122
|
+
|
|
123
|
+
## Resolution
|
|
124
|
+
[How the incident was resolved]
|
|
125
|
+
|
|
126
|
+
## Action Items
|
|
127
|
+
- [ ] [Action item 1]
|
|
128
|
+
- [ ] [Action item 2]
|
|
129
|
+
|
|
130
|
+
## Lessons Learned
|
|
131
|
+
[Key takeaways]
|
|
132
|
+
`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Calculates mean time to resolve (MTTR) for incidents
|
|
137
|
+
* @param {Array} incidents - Resolved incidents with createdAt and resolvedAt
|
|
138
|
+
* @returns {number} MTTR in milliseconds
|
|
139
|
+
*/
|
|
140
|
+
export function calculateMttr(incidents) {
|
|
141
|
+
if (!incidents || incidents.length === 0) {
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const resolvedIncidents = incidents.filter(i => i.resolvedAt);
|
|
146
|
+
if (resolvedIncidents.length === 0) {
|
|
147
|
+
return 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const totalTime = resolvedIncidents.reduce((sum, incident) => {
|
|
151
|
+
const created = new Date(incident.createdAt).getTime();
|
|
152
|
+
const resolved = new Date(incident.resolvedAt).getTime();
|
|
153
|
+
return sum + (resolved - created);
|
|
154
|
+
}, 0);
|
|
155
|
+
|
|
156
|
+
return totalTime / resolvedIncidents.length;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Creates an incident manager instance
|
|
161
|
+
* @returns {Object} Incident manager with methods
|
|
162
|
+
*/
|
|
163
|
+
export function createIncidentManager() {
|
|
164
|
+
const incidents = new Map();
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
/**
|
|
168
|
+
* Creates a new incident
|
|
169
|
+
* @param {Object} options - Incident options
|
|
170
|
+
* @returns {Object} Created incident
|
|
171
|
+
*/
|
|
172
|
+
create(options) {
|
|
173
|
+
const incident = createIncident(options);
|
|
174
|
+
incidents.set(incident.id, incident);
|
|
175
|
+
return incident;
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Updates an existing incident
|
|
180
|
+
* @param {string} id - Incident ID
|
|
181
|
+
* @param {Object} updates - Updates to apply
|
|
182
|
+
* @returns {Object} Updated incident
|
|
183
|
+
*/
|
|
184
|
+
update(id, updates) {
|
|
185
|
+
const incident = incidents.get(id);
|
|
186
|
+
if (!incident) {
|
|
187
|
+
throw new Error(`Incident ${id} not found`);
|
|
188
|
+
}
|
|
189
|
+
const updated = {
|
|
190
|
+
...incident,
|
|
191
|
+
...updates,
|
|
192
|
+
updatedAt: new Date(),
|
|
193
|
+
};
|
|
194
|
+
incidents.set(id, updated);
|
|
195
|
+
return updated;
|
|
196
|
+
},
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Resolves an incident
|
|
200
|
+
* @param {string} id - Incident ID
|
|
201
|
+
* @returns {Object} Resolved incident
|
|
202
|
+
*/
|
|
203
|
+
resolve(id) {
|
|
204
|
+
const incident = incidents.get(id);
|
|
205
|
+
if (!incident) {
|
|
206
|
+
throw new Error(`Incident ${id} not found`);
|
|
207
|
+
}
|
|
208
|
+
const resolved = updateStatus(incident, INCIDENT_STATUS.RESOLVED);
|
|
209
|
+
incidents.set(id, resolved);
|
|
210
|
+
return resolved;
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Gets incident metrics
|
|
215
|
+
* @returns {Object} Metrics including MTTR
|
|
216
|
+
*/
|
|
217
|
+
getMetrics() {
|
|
218
|
+
const all = Array.from(incidents.values());
|
|
219
|
+
const resolved = all.filter(i => i.status === INCIDENT_STATUS.RESOLVED);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
total: all.length,
|
|
223
|
+
open: all.filter(i => i.status === INCIDENT_STATUS.OPEN).length,
|
|
224
|
+
investigating: all.filter(i => i.status === INCIDENT_STATUS.INVESTIGATING).length,
|
|
225
|
+
resolved: resolved.length,
|
|
226
|
+
mttr: calculateMttr(resolved),
|
|
227
|
+
};
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
}
|