qa360 1.4.5 → 2.0.1
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/README.md +1 -1
- package/dist/commands/ai.d.ts +41 -0
- package/dist/commands/ai.js +499 -0
- package/dist/commands/ask.js +12 -12
- package/dist/commands/coverage.d.ts +8 -0
- package/dist/commands/coverage.js +252 -0
- package/dist/commands/explain.d.ts +27 -0
- package/dist/commands/explain.js +630 -0
- package/dist/commands/flakiness.d.ts +73 -0
- package/dist/commands/flakiness.js +435 -0
- package/dist/commands/generate.d.ts +66 -0
- package/dist/commands/generate.js +438 -0
- package/dist/commands/init.d.ts +56 -9
- package/dist/commands/init.js +217 -10
- package/dist/commands/monitor.d.ts +27 -0
- package/dist/commands/monitor.js +225 -0
- package/dist/commands/ollama.d.ts +40 -0
- package/dist/commands/ollama.js +301 -0
- package/dist/commands/pack.d.ts +37 -9
- package/dist/commands/pack.js +240 -141
- package/dist/commands/regression.d.ts +8 -0
- package/dist/commands/regression.js +340 -0
- package/dist/commands/repair.d.ts +26 -0
- package/dist/commands/repair.js +307 -0
- package/dist/commands/retry.d.ts +43 -0
- package/dist/commands/retry.js +275 -0
- package/dist/commands/run.d.ts +8 -3
- package/dist/commands/run.js +45 -31
- package/dist/commands/slo.d.ts +8 -0
- package/dist/commands/slo.js +327 -0
- package/dist/core/adapters/playwright-native-api.d.ts +183 -0
- package/dist/core/adapters/playwright-native-api.js +461 -0
- package/dist/core/adapters/playwright-ui.d.ts +7 -0
- package/dist/core/adapters/playwright-ui.js +29 -1
- package/dist/core/ai/anthropic-provider.d.ts +50 -0
- package/dist/core/ai/anthropic-provider.js +211 -0
- package/dist/core/ai/deepseek-provider.d.ts +81 -0
- package/dist/core/ai/deepseek-provider.js +254 -0
- package/dist/core/ai/index.d.ts +60 -0
- package/dist/core/ai/index.js +18 -0
- package/dist/core/ai/llm-client.d.ts +45 -0
- package/dist/core/ai/llm-client.js +7 -0
- package/dist/core/ai/mock-provider.d.ts +49 -0
- package/dist/core/ai/mock-provider.js +121 -0
- package/dist/core/ai/ollama-provider.d.ts +78 -0
- package/dist/core/ai/ollama-provider.js +192 -0
- package/dist/core/ai/openai-provider.d.ts +48 -0
- package/dist/core/ai/openai-provider.js +188 -0
- package/dist/core/ai/provider-factory.d.ts +160 -0
- package/dist/core/ai/provider-factory.js +269 -0
- package/dist/core/auth/api-key-provider.d.ts +16 -0
- package/dist/core/auth/api-key-provider.js +63 -0
- package/dist/core/auth/aws-iam-provider.d.ts +35 -0
- package/dist/core/auth/aws-iam-provider.js +177 -0
- package/dist/core/auth/azure-ad-provider.d.ts +15 -0
- package/dist/core/auth/azure-ad-provider.js +99 -0
- package/dist/core/auth/basic-auth-provider.d.ts +26 -0
- package/dist/core/auth/basic-auth-provider.js +111 -0
- package/dist/core/auth/gcp-adc-provider.d.ts +27 -0
- package/dist/core/auth/gcp-adc-provider.js +126 -0
- package/dist/core/auth/index.d.ts +238 -0
- package/dist/core/auth/index.js +82 -0
- package/dist/core/auth/jwt-provider.d.ts +19 -0
- package/dist/core/auth/jwt-provider.js +160 -0
- package/dist/core/auth/manager.d.ts +84 -0
- package/dist/core/auth/manager.js +230 -0
- package/dist/core/auth/oauth2-provider.d.ts +17 -0
- package/dist/core/auth/oauth2-provider.js +114 -0
- package/dist/core/auth/totp-provider.d.ts +31 -0
- package/dist/core/auth/totp-provider.js +134 -0
- package/dist/core/auth/ui-login-provider.d.ts +26 -0
- package/dist/core/auth/ui-login-provider.js +198 -0
- package/dist/core/cache/index.d.ts +7 -0
- package/dist/core/cache/index.js +6 -0
- package/dist/core/cache/lru-cache.d.ts +203 -0
- package/dist/core/cache/lru-cache.js +397 -0
- package/dist/core/coverage/analyzer.d.ts +101 -0
- package/dist/core/coverage/analyzer.js +415 -0
- package/dist/core/coverage/collector.d.ts +74 -0
- package/dist/core/coverage/collector.js +459 -0
- package/dist/core/coverage/config.d.ts +37 -0
- package/dist/core/coverage/config.js +156 -0
- package/dist/core/coverage/index.d.ts +11 -0
- package/dist/core/coverage/index.js +15 -0
- package/dist/core/coverage/types.d.ts +267 -0
- package/dist/core/coverage/types.js +6 -0
- package/dist/core/coverage/vault.d.ts +95 -0
- package/dist/core/coverage/vault.js +405 -0
- package/dist/core/dashboard/assets.d.ts +6 -0
- package/dist/core/dashboard/assets.js +690 -0
- package/dist/core/dashboard/index.d.ts +6 -0
- package/dist/core/dashboard/index.js +5 -0
- package/dist/core/dashboard/server.d.ts +72 -0
- package/dist/core/dashboard/server.js +354 -0
- package/dist/core/dashboard/types.d.ts +70 -0
- package/dist/core/dashboard/types.js +5 -0
- package/dist/core/discoverer/index.d.ts +115 -0
- package/dist/core/discoverer/index.js +250 -0
- package/dist/core/flakiness/index.d.ts +228 -0
- package/dist/core/flakiness/index.js +384 -0
- package/dist/core/generation/code-formatter.d.ts +111 -0
- package/dist/core/generation/code-formatter.js +307 -0
- package/dist/core/generation/code-generator.d.ts +144 -0
- package/dist/core/generation/code-generator.js +293 -0
- package/dist/core/generation/generator.d.ts +40 -0
- package/dist/core/generation/generator.js +76 -0
- package/dist/core/generation/index.d.ts +30 -0
- package/dist/core/generation/index.js +28 -0
- package/dist/core/generation/pack-generator.d.ts +107 -0
- package/dist/core/generation/pack-generator.js +416 -0
- package/dist/core/generation/prompt-builder.d.ts +132 -0
- package/dist/core/generation/prompt-builder.js +672 -0
- package/dist/core/generation/source-analyzer.d.ts +213 -0
- package/dist/core/generation/source-analyzer.js +657 -0
- package/dist/core/generation/test-optimizer.d.ts +117 -0
- package/dist/core/generation/test-optimizer.js +328 -0
- package/dist/core/generation/types.d.ts +214 -0
- package/dist/core/generation/types.js +4 -0
- package/dist/core/index.d.ts +23 -1
- package/dist/core/index.js +39 -0
- package/dist/core/pack/validator.js +31 -1
- package/dist/core/pack-v2/index.d.ts +9 -0
- package/dist/core/pack-v2/index.js +8 -0
- package/dist/core/pack-v2/loader.d.ts +62 -0
- package/dist/core/pack-v2/loader.js +231 -0
- package/dist/core/pack-v2/migrator.d.ts +56 -0
- package/dist/core/pack-v2/migrator.js +455 -0
- package/dist/core/pack-v2/validator.d.ts +61 -0
- package/dist/core/pack-v2/validator.js +577 -0
- package/dist/core/regression/detector.d.ts +107 -0
- package/dist/core/regression/detector.js +497 -0
- package/dist/core/regression/index.d.ts +9 -0
- package/dist/core/regression/index.js +11 -0
- package/dist/core/regression/trend-analyzer.d.ts +102 -0
- package/dist/core/regression/trend-analyzer.js +345 -0
- package/dist/core/regression/types.d.ts +222 -0
- package/dist/core/regression/types.js +7 -0
- package/dist/core/regression/vault.d.ts +87 -0
- package/dist/core/regression/vault.js +289 -0
- package/dist/core/repair/engine/fixer.d.ts +24 -0
- package/dist/core/repair/engine/fixer.js +226 -0
- package/dist/core/repair/engine/suggestion-engine.d.ts +18 -0
- package/dist/core/repair/engine/suggestion-engine.js +187 -0
- package/dist/core/repair/index.d.ts +10 -0
- package/dist/core/repair/index.js +13 -0
- package/dist/core/repair/repairer.d.ts +90 -0
- package/dist/core/repair/repairer.js +284 -0
- package/dist/core/repair/types.d.ts +91 -0
- package/dist/core/repair/types.js +6 -0
- package/dist/core/repair/utils/error-analyzer.d.ts +28 -0
- package/dist/core/repair/utils/error-analyzer.js +264 -0
- package/dist/core/retry/flakiness-integration.d.ts +60 -0
- package/dist/core/retry/flakiness-integration.js +228 -0
- package/dist/core/retry/index.d.ts +14 -0
- package/dist/core/retry/index.js +16 -0
- package/dist/core/retry/retry-engine.d.ts +80 -0
- package/dist/core/retry/retry-engine.js +296 -0
- package/dist/core/retry/types.d.ts +178 -0
- package/dist/core/retry/types.js +52 -0
- package/dist/core/retry/vault.d.ts +77 -0
- package/dist/core/retry/vault.js +304 -0
- package/dist/core/runner/e2e-helpers.d.ts +102 -0
- package/dist/core/runner/e2e-helpers.js +153 -0
- package/dist/core/runner/phase3-runner.d.ts +101 -2
- package/dist/core/runner/phase3-runner.js +559 -24
- package/dist/core/self-healing/assertion-healer.d.ts +97 -0
- package/dist/core/self-healing/assertion-healer.js +371 -0
- package/dist/core/self-healing/engine.d.ts +122 -0
- package/dist/core/self-healing/engine.js +538 -0
- package/dist/core/self-healing/index.d.ts +10 -0
- package/dist/core/self-healing/index.js +11 -0
- package/dist/core/self-healing/selector-healer.d.ts +103 -0
- package/dist/core/self-healing/selector-healer.js +372 -0
- package/dist/core/self-healing/types.d.ts +152 -0
- package/dist/core/self-healing/types.js +6 -0
- package/dist/core/slo/config.d.ts +107 -0
- package/dist/core/slo/config.js +360 -0
- package/dist/core/slo/index.d.ts +11 -0
- package/dist/core/slo/index.js +15 -0
- package/dist/core/slo/sli-calculator.d.ts +92 -0
- package/dist/core/slo/sli-calculator.js +364 -0
- package/dist/core/slo/slo-tracker.d.ts +148 -0
- package/dist/core/slo/slo-tracker.js +379 -0
- package/dist/core/slo/types.d.ts +281 -0
- package/dist/core/slo/types.js +7 -0
- package/dist/core/slo/vault.d.ts +102 -0
- package/dist/core/slo/vault.js +427 -0
- package/dist/core/tui/index.d.ts +7 -0
- package/dist/core/tui/index.js +6 -0
- package/dist/core/tui/monitor.d.ts +92 -0
- package/dist/core/tui/monitor.js +271 -0
- package/dist/core/tui/renderer.d.ts +33 -0
- package/dist/core/tui/renderer.js +218 -0
- package/dist/core/tui/types.d.ts +63 -0
- package/dist/core/tui/types.js +5 -0
- package/dist/core/types/pack-v2.d.ts +425 -0
- package/dist/core/types/pack-v2.js +8 -0
- package/dist/core/vault/index.d.ts +116 -0
- package/dist/core/vault/index.js +400 -5
- package/dist/core/watch/index.d.ts +7 -0
- package/dist/core/watch/index.js +6 -0
- package/dist/core/watch/watch-mode.d.ts +213 -0
- package/dist/core/watch/watch-mode.js +389 -0
- package/dist/index.js +68 -68
- package/dist/utils/config.d.ts +5 -0
- package/dist/utils/config.js +136 -0
- package/package.json +5 -1
- package/dist/core/adapters/playwright-api.d.ts +0 -82
- package/dist/core/adapters/playwright-api.js +0 -264
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SLO Tracker
|
|
3
|
+
*
|
|
4
|
+
* Tracks Service Level Objectives and manages error budgets.
|
|
5
|
+
* Monitors SLO compliance and generates reports.
|
|
6
|
+
*/
|
|
7
|
+
import { SLICalculator } from './sli-calculator.js';
|
|
8
|
+
/**
|
|
9
|
+
* SLO Tracker class
|
|
10
|
+
*/
|
|
11
|
+
export class SLOTracker {
|
|
12
|
+
slos = new Map();
|
|
13
|
+
slis = new Map();
|
|
14
|
+
measurements = new Map();
|
|
15
|
+
violations = [];
|
|
16
|
+
calculator;
|
|
17
|
+
options;
|
|
18
|
+
constructor(options = {}) {
|
|
19
|
+
this.calculator = new SLICalculator();
|
|
20
|
+
this.options = {
|
|
21
|
+
warningThreshold: options.warningThreshold ?? 0.95,
|
|
22
|
+
trackViolations: options.trackViolations ?? true,
|
|
23
|
+
enableAlerts: options.enableAlerts ?? true,
|
|
24
|
+
onAlert: options.onAlert ?? (() => { })
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Register an SLO
|
|
29
|
+
*/
|
|
30
|
+
registerSLO(slo) {
|
|
31
|
+
this.slos.set(slo.id, slo);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Register an SLI
|
|
35
|
+
*/
|
|
36
|
+
registerSLI(sli) {
|
|
37
|
+
this.slis.set(sli.id, sli);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Register multiple SLOs
|
|
41
|
+
*/
|
|
42
|
+
registerSLOs(slos) {
|
|
43
|
+
for (const slo of slos) {
|
|
44
|
+
this.registerSLO(slo);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Register multiple SLIs
|
|
49
|
+
*/
|
|
50
|
+
registerSLIs(slis) {
|
|
51
|
+
for (const sli of slis) {
|
|
52
|
+
this.registerSLI(sli);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Get an SLO by ID
|
|
57
|
+
*/
|
|
58
|
+
getSLO(id) {
|
|
59
|
+
return this.slos.get(id);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get an SLI by ID
|
|
63
|
+
*/
|
|
64
|
+
getSLI(id) {
|
|
65
|
+
return this.slis.get(id);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get all registered SLOs
|
|
69
|
+
*/
|
|
70
|
+
getAllSLOs() {
|
|
71
|
+
return Array.from(this.slos.values());
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get all registered SLIs
|
|
75
|
+
*/
|
|
76
|
+
getAllSLIs() {
|
|
77
|
+
return Array.from(this.slis.values());
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Record a measurement for an SLI
|
|
81
|
+
*/
|
|
82
|
+
recordMeasurement(measurement) {
|
|
83
|
+
const sli = this.slis.get(measurement.sliId);
|
|
84
|
+
if (!sli) {
|
|
85
|
+
throw new Error(`SLI not found: ${measurement.sliId}`);
|
|
86
|
+
}
|
|
87
|
+
if (!this.measurements.has(measurement.sliId)) {
|
|
88
|
+
this.measurements.set(measurement.sliId, []);
|
|
89
|
+
}
|
|
90
|
+
const measurements = this.measurements.get(measurement.sliId);
|
|
91
|
+
measurements.push(measurement);
|
|
92
|
+
// Keep only measurements within the longest SLO window
|
|
93
|
+
const longestWindow = this.getLongestWindow();
|
|
94
|
+
const cutoff = Date.now() - longestWindow;
|
|
95
|
+
const filtered = measurements.filter(m => m.timestamp >= cutoff);
|
|
96
|
+
this.measurements.set(measurement.sliId, filtered);
|
|
97
|
+
// Update dependent SLOs
|
|
98
|
+
this.updateDependentSLOs(measurement.sliId);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get measurements for an SLI
|
|
102
|
+
*/
|
|
103
|
+
getMeasurements(sliId, windowMs) {
|
|
104
|
+
const measurements = this.measurements.get(sliId) || [];
|
|
105
|
+
if (!windowMs) {
|
|
106
|
+
return measurements;
|
|
107
|
+
}
|
|
108
|
+
const cutoff = Date.now() - windowMs;
|
|
109
|
+
return measurements.filter(m => m.timestamp >= cutoff);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Calculate SLO result
|
|
113
|
+
*/
|
|
114
|
+
calculateSLOResult(sloId) {
|
|
115
|
+
const slo = this.slos.get(sloId);
|
|
116
|
+
if (!slo) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
// Get measurements for all SLIs in the SLO
|
|
120
|
+
const windowStart = Date.now() - slo.windowMs;
|
|
121
|
+
const sliResults = [];
|
|
122
|
+
for (const sliId of slo.sliIds) {
|
|
123
|
+
const sli = this.slis.get(sliId);
|
|
124
|
+
if (!sli)
|
|
125
|
+
continue;
|
|
126
|
+
const measurements = this.getMeasurements(sliId, slo.windowMs);
|
|
127
|
+
if (measurements.length === 0)
|
|
128
|
+
continue;
|
|
129
|
+
// Get the most recent measurement or aggregate
|
|
130
|
+
const latest = measurements[measurements.length - 1];
|
|
131
|
+
sliResults.push({
|
|
132
|
+
sliId: sli.id,
|
|
133
|
+
name: sli.name,
|
|
134
|
+
value: latest.value,
|
|
135
|
+
target: slo.target,
|
|
136
|
+
contribution: 1 / slo.sliIds.length // Equal weight by default
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (sliResults.length === 0) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
// Calculate weighted average for SLO value
|
|
143
|
+
const current = sliResults.reduce((sum, s) => sum + s.value * s.contribution, 0);
|
|
144
|
+
// Calculate error budget
|
|
145
|
+
const errorBudget = this.calculateErrorBudget(slo, current);
|
|
146
|
+
// Determine status
|
|
147
|
+
const status = this.calculator.determineStatus(current, slo.target, this.options.warningThreshold);
|
|
148
|
+
// Update SLO
|
|
149
|
+
slo.currentValue = current;
|
|
150
|
+
slo.status = status;
|
|
151
|
+
slo.errorBudget = errorBudget;
|
|
152
|
+
return {
|
|
153
|
+
sloId: slo.id,
|
|
154
|
+
name: slo.name,
|
|
155
|
+
target: slo.target,
|
|
156
|
+
current,
|
|
157
|
+
status,
|
|
158
|
+
errorBudget: {
|
|
159
|
+
initial: errorBudget.initialBudget,
|
|
160
|
+
remaining: errorBudget.remainingBudget,
|
|
161
|
+
burned: errorBudget.initialBudget - errorBudget.remainingBudget,
|
|
162
|
+
burnRate: errorBudget.remainingBudget > 0
|
|
163
|
+
? (errorBudget.initialBudget - errorBudget.remainingBudget) / errorBudget.initialBudget
|
|
164
|
+
: 1
|
|
165
|
+
},
|
|
166
|
+
slis: sliResults
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Calculate all SLO results
|
|
171
|
+
*/
|
|
172
|
+
calculateAllResults() {
|
|
173
|
+
const results = [];
|
|
174
|
+
for (const sloId of this.slos.keys()) {
|
|
175
|
+
const result = this.calculateSLOResult(sloId);
|
|
176
|
+
if (result) {
|
|
177
|
+
results.push(result);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return results;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Generate SLO report
|
|
184
|
+
*/
|
|
185
|
+
generateReport(period) {
|
|
186
|
+
const results = this.calculateAllResults();
|
|
187
|
+
const summary = {
|
|
188
|
+
total: results.length,
|
|
189
|
+
healthy: results.filter(r => r.status === 'healthy').length,
|
|
190
|
+
warning: results.filter(r => r.status === 'warning').length,
|
|
191
|
+
breached: results.filter(r => r.status === 'breached').length
|
|
192
|
+
};
|
|
193
|
+
const overallStatus = summary.breached > 0 ? 'breached'
|
|
194
|
+
: summary.warning > 0 ? 'warning'
|
|
195
|
+
: summary.healthy > 0 ? 'healthy'
|
|
196
|
+
: 'unknown';
|
|
197
|
+
return {
|
|
198
|
+
id: `report_${Date.now()}`,
|
|
199
|
+
timestamp: Date.now(),
|
|
200
|
+
period: period || {
|
|
201
|
+
start: Date.now() - this.getLongestWindow(),
|
|
202
|
+
end: Date.now()
|
|
203
|
+
},
|
|
204
|
+
slos: results,
|
|
205
|
+
status: overallStatus,
|
|
206
|
+
summary
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Get error budget for an SLO
|
|
211
|
+
*/
|
|
212
|
+
getErrorBudget(sloId) {
|
|
213
|
+
const slo = this.slos.get(sloId);
|
|
214
|
+
if (!slo) {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
const result = this.calculateSLOResult(sloId);
|
|
218
|
+
if (!result) {
|
|
219
|
+
return slo.errorBudget;
|
|
220
|
+
}
|
|
221
|
+
return slo.errorBudget;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Calculate burn rate for an SLO
|
|
225
|
+
*/
|
|
226
|
+
calculateBurnRate(sloId) {
|
|
227
|
+
const slo = this.slos.get(sloId);
|
|
228
|
+
if (!slo) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
// Get all measurements for SLIs in this SLO
|
|
232
|
+
const allMeasurements = [];
|
|
233
|
+
for (const sliId of slo.sliIds) {
|
|
234
|
+
const measurements = this.getMeasurements(sliId, slo.windowMs);
|
|
235
|
+
allMeasurements.push(...measurements);
|
|
236
|
+
}
|
|
237
|
+
if (allMeasurements.length === 0) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
return this.calculator.calculateBurnRate(allMeasurements, slo.target, slo.windowMs);
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Get violations for an SLO
|
|
244
|
+
*/
|
|
245
|
+
getViolations(sloId) {
|
|
246
|
+
if (sloId) {
|
|
247
|
+
return this.violations.filter(v => v.sloId === sloId);
|
|
248
|
+
}
|
|
249
|
+
return this.violations;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Clear violations
|
|
253
|
+
*/
|
|
254
|
+
clearViolations(sloId) {
|
|
255
|
+
if (sloId) {
|
|
256
|
+
this.violations = this.violations.filter(v => v.sloId !== sloId);
|
|
257
|
+
}
|
|
258
|
+
else {
|
|
259
|
+
this.violations = [];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Resolve a violation
|
|
264
|
+
*/
|
|
265
|
+
resolveViolation(violationId) {
|
|
266
|
+
const violation = this.violations.find(v => v.id === violationId);
|
|
267
|
+
if (violation) {
|
|
268
|
+
violation.resolved = true;
|
|
269
|
+
violation.resolvedAt = Date.now();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Calculate error budget from current value and target
|
|
274
|
+
*/
|
|
275
|
+
calculateErrorBudget(slo, currentValue) {
|
|
276
|
+
const initialBudget = 100 - slo.target;
|
|
277
|
+
const burned = Math.max(0, slo.target - currentValue);
|
|
278
|
+
const remaining = Math.max(0, initialBudget - burned);
|
|
279
|
+
return {
|
|
280
|
+
...slo.errorBudget,
|
|
281
|
+
initialBudget,
|
|
282
|
+
remainingBudget: remaining
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Update SLOs that depend on an SLI
|
|
287
|
+
*/
|
|
288
|
+
updateDependentSLOs(sliId) {
|
|
289
|
+
for (const slo of this.slos.values()) {
|
|
290
|
+
if (!slo.sliIds.includes(sliId))
|
|
291
|
+
continue;
|
|
292
|
+
const result = this.calculateSLOResult(slo.id);
|
|
293
|
+
if (!result)
|
|
294
|
+
continue;
|
|
295
|
+
// Check for violations
|
|
296
|
+
if (this.options.trackViolations) {
|
|
297
|
+
this.checkForViolation(slo, result);
|
|
298
|
+
}
|
|
299
|
+
// Generate alerts
|
|
300
|
+
if (this.options.enableAlerts) {
|
|
301
|
+
this.generateAlerts(slo, result);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Check for SLO violation
|
|
307
|
+
*/
|
|
308
|
+
checkForViolation(slo, result) {
|
|
309
|
+
const now = Date.now();
|
|
310
|
+
// Check if we just breached
|
|
311
|
+
if (result.status === 'breached' && slo.status !== 'breached') {
|
|
312
|
+
this.violations.push({
|
|
313
|
+
id: `violation_${now}_${slo.id}`,
|
|
314
|
+
sloId: slo.id,
|
|
315
|
+
timestamp: now,
|
|
316
|
+
value: result.current,
|
|
317
|
+
expected: result.target,
|
|
318
|
+
severity: 'critical',
|
|
319
|
+
resolved: false
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
// Check if we just hit warning
|
|
323
|
+
if (result.status === 'warning' && slo.status === 'healthy') {
|
|
324
|
+
this.violations.push({
|
|
325
|
+
id: `warning_${now}_${slo.id}`,
|
|
326
|
+
sloId: slo.id,
|
|
327
|
+
timestamp: now,
|
|
328
|
+
value: result.current,
|
|
329
|
+
expected: result.target,
|
|
330
|
+
severity: 'warning',
|
|
331
|
+
resolved: false
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Generate alerts for SLO status
|
|
337
|
+
*/
|
|
338
|
+
generateAlerts(slo, result) {
|
|
339
|
+
if (result.status === 'breached' || result.status === 'warning') {
|
|
340
|
+
this.options.onAlert({
|
|
341
|
+
sloId: slo.id,
|
|
342
|
+
sloName: slo.name,
|
|
343
|
+
severity: result.status === 'breached' ? 'critical' : 'warning',
|
|
344
|
+
message: `SLO "${slo.name}" is ${result.status}: ${result.current.toFixed(2)}% < ${result.target}%`,
|
|
345
|
+
currentValue: result.current,
|
|
346
|
+
target: result.target,
|
|
347
|
+
errorBudgetRemaining: result.errorBudget.remaining,
|
|
348
|
+
timestamp: Date.now()
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Get the longest window from all SLOs
|
|
354
|
+
*/
|
|
355
|
+
getLongestWindow() {
|
|
356
|
+
let max = 0;
|
|
357
|
+
for (const slo of this.slos.values()) {
|
|
358
|
+
if (slo.windowMs > max) {
|
|
359
|
+
max = slo.windowMs;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return max || 86400000 * 30; // Default 30 days
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Clear all data
|
|
366
|
+
*/
|
|
367
|
+
clear() {
|
|
368
|
+
this.slos.clear();
|
|
369
|
+
this.slis.clear();
|
|
370
|
+
this.measurements.clear();
|
|
371
|
+
this.violations = [];
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Create an SLO tracker
|
|
376
|
+
*/
|
|
377
|
+
export function createSLOTracker(options) {
|
|
378
|
+
return new SLOTracker(options);
|
|
379
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QA360 SLO/SLI Management Module
|
|
3
|
+
*
|
|
4
|
+
* Types for Service Level Objectives and Service Level Indicators.
|
|
5
|
+
* Based on Google SRE methodology for reliability engineering.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* SLO (Service Level Objective) - Target goal for reliability
|
|
9
|
+
*/
|
|
10
|
+
export interface SLO {
|
|
11
|
+
/** Unique identifier */
|
|
12
|
+
id: string;
|
|
13
|
+
/** Display name */
|
|
14
|
+
name: string;
|
|
15
|
+
/** Description */
|
|
16
|
+
description?: string;
|
|
17
|
+
/** SLIs that contribute to this SLO */
|
|
18
|
+
sliIds: string[];
|
|
19
|
+
/** Target percentage (0-100) */
|
|
20
|
+
target: number;
|
|
21
|
+
/** Rolling window duration in milliseconds */
|
|
22
|
+
windowMs: number;
|
|
23
|
+
/** Error budget configuration */
|
|
24
|
+
errorBudget: ErrorBudget;
|
|
25
|
+
/** SLO status */
|
|
26
|
+
status: SLOStatus;
|
|
27
|
+
/** Current achievement */
|
|
28
|
+
currentValue?: number;
|
|
29
|
+
/** Metadata */
|
|
30
|
+
tags?: string[];
|
|
31
|
+
/** Owner/team */
|
|
32
|
+
owner?: string;
|
|
33
|
+
/** Creation timestamp */
|
|
34
|
+
createdAt: number;
|
|
35
|
+
/** Last update timestamp */
|
|
36
|
+
updatedAt: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Error Budget Configuration
|
|
40
|
+
*/
|
|
41
|
+
export interface ErrorBudget {
|
|
42
|
+
/** Initial budget percentage (100 - target) */
|
|
43
|
+
initialBudget: number;
|
|
44
|
+
/** Remaining budget percentage */
|
|
45
|
+
remainingBudget: number;
|
|
46
|
+
/** Burn rate alerting thresholds */
|
|
47
|
+
alertThresholds: {
|
|
48
|
+
warning: number;
|
|
49
|
+
critical: number;
|
|
50
|
+
};
|
|
51
|
+
/** Actions when budget is exhausted */
|
|
52
|
+
onExhaustion: 'stop_deployments' | 'alert_only' | 'freeze_features' | 'custom';
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* SLO Status
|
|
56
|
+
*/
|
|
57
|
+
export type SLOStatus = 'healthy' | 'warning' | 'breached' | 'unknown';
|
|
58
|
+
/**
|
|
59
|
+
* SLI (Service Level Indicator) - Metric that measures reliability
|
|
60
|
+
*/
|
|
61
|
+
export interface SLI {
|
|
62
|
+
/** Unique identifier */
|
|
63
|
+
id: string;
|
|
64
|
+
/** Display name */
|
|
65
|
+
name: string;
|
|
66
|
+
/** Description */
|
|
67
|
+
description?: string;
|
|
68
|
+
/** SLI type */
|
|
69
|
+
type: SLIType;
|
|
70
|
+
/** Metric source */
|
|
71
|
+
source: SLISource;
|
|
72
|
+
/** Query/fetch configuration */
|
|
73
|
+
query: SLIQuery;
|
|
74
|
+
/** Aggregation method */
|
|
75
|
+
aggregation: SLIAggregation;
|
|
76
|
+
/** Unit of measurement */
|
|
77
|
+
unit: SLIUnit;
|
|
78
|
+
/** Good/bad event definitions */
|
|
79
|
+
threshold: SLIThreshold;
|
|
80
|
+
/** SLI category */
|
|
81
|
+
category: SLICategory;
|
|
82
|
+
/** Metadata */
|
|
83
|
+
tags?: string[];
|
|
84
|
+
/** Creation timestamp */
|
|
85
|
+
createdAt: number;
|
|
86
|
+
/** Last update timestamp */
|
|
87
|
+
updatedAt: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* SLI Types
|
|
91
|
+
*/
|
|
92
|
+
export type SLIType = 'availability' | 'latency' | 'error_rate' | 'throughput' | 'durability' | 'coverage' | 'performance' | 'quality' | 'custom';
|
|
93
|
+
/**
|
|
94
|
+
* SLI Data Sources
|
|
95
|
+
*/
|
|
96
|
+
export type SLISource = 'qa360_tests' | 'prometheus' | 'cloudwatch' | 'datadog' | 'grafana' | 'custom_api' | 'file' | 'manual';
|
|
97
|
+
/**
|
|
98
|
+
* SLI Query Configuration
|
|
99
|
+
*/
|
|
100
|
+
export interface SLIQuery {
|
|
101
|
+
/** Query string for the metric source */
|
|
102
|
+
queryString: string;
|
|
103
|
+
/** Filters to apply */
|
|
104
|
+
filters?: Record<string, string>;
|
|
105
|
+
/** Grouping keys */
|
|
106
|
+
groupBy?: string[];
|
|
107
|
+
/** Time window for aggregation */
|
|
108
|
+
timeWindow?: {
|
|
109
|
+
value: number;
|
|
110
|
+
unit: 'ms' | 's' | 'm' | 'h' | 'd';
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* SLI Aggregation Methods
|
|
115
|
+
*/
|
|
116
|
+
export type SLIAggregation = 'count' | 'sum' | 'avg' | 'p50' | 'p95' | 'p99' | 'p99_9' | 'max' | 'min' | 'rate' | 'ratio';
|
|
117
|
+
/**
|
|
118
|
+
* SLI Units
|
|
119
|
+
*/
|
|
120
|
+
export type SLIUnit = 'percentage' | 'milliseconds' | 'seconds' | 'count' | 'bytes' | 'requests_per_sec' | 'ratio' | 'boolean';
|
|
121
|
+
/**
|
|
122
|
+
* SLI Threshold Definition
|
|
123
|
+
*/
|
|
124
|
+
export interface SLIThreshold {
|
|
125
|
+
/** Comparison operator */
|
|
126
|
+
operator: 'gt' | 'gte' | 'lt' | 'lte' | 'eq' | 'ne';
|
|
127
|
+
/** Threshold value */
|
|
128
|
+
value: number;
|
|
129
|
+
/** Optional upper bound for range checks */
|
|
130
|
+
upperBound?: number;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* SLI Categories
|
|
134
|
+
*/
|
|
135
|
+
export type SLICategory = 'reliability' | 'performance' | 'quality' | 'security' | 'operational' | 'custom';
|
|
136
|
+
/**
|
|
137
|
+
* SLI Measurement Result
|
|
138
|
+
*/
|
|
139
|
+
export interface SLIMeasurement {
|
|
140
|
+
/** SLI identifier */
|
|
141
|
+
sliId: string;
|
|
142
|
+
/** Timestamp of measurement */
|
|
143
|
+
timestamp: number;
|
|
144
|
+
/** Measured value */
|
|
145
|
+
value: number;
|
|
146
|
+
/** Total events (for ratios) */
|
|
147
|
+
total?: number;
|
|
148
|
+
/** Good events (for ratios) */
|
|
149
|
+
good?: number;
|
|
150
|
+
/** Bad events (for ratios) */
|
|
151
|
+
bad?: number;
|
|
152
|
+
/** Unit */
|
|
153
|
+
unit: SLIUnit;
|
|
154
|
+
/** Window start */
|
|
155
|
+
windowStart: number;
|
|
156
|
+
/** Window end: number */
|
|
157
|
+
windowEnd: number;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* SLO Report
|
|
161
|
+
*/
|
|
162
|
+
export interface SLOReport {
|
|
163
|
+
/** Report identifier */
|
|
164
|
+
id: string;
|
|
165
|
+
/** Timestamp */
|
|
166
|
+
timestamp: number;
|
|
167
|
+
/** Reporting period */
|
|
168
|
+
period: {
|
|
169
|
+
start: number;
|
|
170
|
+
end: number;
|
|
171
|
+
};
|
|
172
|
+
/** SLO results */
|
|
173
|
+
slos: SLOResult[];
|
|
174
|
+
/** Overall status */
|
|
175
|
+
status: SLOStatus;
|
|
176
|
+
/** Summary metrics */
|
|
177
|
+
summary: {
|
|
178
|
+
total: number;
|
|
179
|
+
healthy: number;
|
|
180
|
+
warning: number;
|
|
181
|
+
breached: number;
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* SLO Result for a single SLO
|
|
186
|
+
*/
|
|
187
|
+
export interface SLOResult {
|
|
188
|
+
/** SLO identifier */
|
|
189
|
+
sloId: string;
|
|
190
|
+
/** SLO name */
|
|
191
|
+
name: string;
|
|
192
|
+
/** Target value */
|
|
193
|
+
target: number;
|
|
194
|
+
/** Current value */
|
|
195
|
+
current: number;
|
|
196
|
+
/** Achievement status */
|
|
197
|
+
status: SLOStatus;
|
|
198
|
+
/** Error budget info */
|
|
199
|
+
errorBudget: {
|
|
200
|
+
initial: number;
|
|
201
|
+
remaining: number;
|
|
202
|
+
burned: number;
|
|
203
|
+
burnRate: number;
|
|
204
|
+
};
|
|
205
|
+
/** Contributing SLI results */
|
|
206
|
+
slis: {
|
|
207
|
+
sliId: string;
|
|
208
|
+
name: string;
|
|
209
|
+
value: number;
|
|
210
|
+
target: number;
|
|
211
|
+
contribution: number;
|
|
212
|
+
}[];
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Error Budget Burn Rate
|
|
216
|
+
*/
|
|
217
|
+
export interface BurnRate {
|
|
218
|
+
/** Current burn rate (multiple of normal) */
|
|
219
|
+
current: number;
|
|
220
|
+
/** Status */
|
|
221
|
+
status: 'healthy' | 'warning' | 'critical';
|
|
222
|
+
/** Projected exhaustion */
|
|
223
|
+
projectedExhaustion?: number;
|
|
224
|
+
/** Historical burn rates */
|
|
225
|
+
history: Array<{
|
|
226
|
+
timestamp: number;
|
|
227
|
+
rate: number;
|
|
228
|
+
}>;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* SLO Configuration
|
|
232
|
+
*/
|
|
233
|
+
export interface SLOConfig {
|
|
234
|
+
/** Enable SLO/SLI tracking */
|
|
235
|
+
enabled: boolean;
|
|
236
|
+
/** Default rolling window */
|
|
237
|
+
defaultWindowMs: number;
|
|
238
|
+
/** Default error budget behavior */
|
|
239
|
+
defaultOnExhaustion: ErrorBudget['onExhaustion'];
|
|
240
|
+
/** Alert thresholds */
|
|
241
|
+
alertThresholds: {
|
|
242
|
+
warning: number;
|
|
243
|
+
critical: number;
|
|
244
|
+
};
|
|
245
|
+
/** Data retention */
|
|
246
|
+
retention: {
|
|
247
|
+
measurements: number;
|
|
248
|
+
aggregated: number;
|
|
249
|
+
};
|
|
250
|
+
/** SLO definitions */
|
|
251
|
+
slos: SLO[];
|
|
252
|
+
/** SLI definitions */
|
|
253
|
+
slis: SLI[];
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* SLO Violation Event
|
|
257
|
+
*/
|
|
258
|
+
export interface SLOViolation {
|
|
259
|
+
/** Unique identifier */
|
|
260
|
+
id: string;
|
|
261
|
+
/** SLO that was violated */
|
|
262
|
+
sloId: string;
|
|
263
|
+
/** Timestamp of violation */
|
|
264
|
+
timestamp: number;
|
|
265
|
+
/** Value at violation */
|
|
266
|
+
value: number;
|
|
267
|
+
/** Expected value */
|
|
268
|
+
expected: number;
|
|
269
|
+
/** Severity */
|
|
270
|
+
severity: 'warning' | 'critical';
|
|
271
|
+
/** Context */
|
|
272
|
+
context?: {
|
|
273
|
+
runId?: string;
|
|
274
|
+
gate?: string;
|
|
275
|
+
environment?: string;
|
|
276
|
+
};
|
|
277
|
+
/** Resolved flag */
|
|
278
|
+
resolved: boolean;
|
|
279
|
+
/** Resolution timestamp */
|
|
280
|
+
resolvedAt?: number;
|
|
281
|
+
}
|