@synth-deploy/server 0.1.0 → 1.1.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/dist/agent/envoy-client.d.ts +62 -7
- package/dist/agent/envoy-client.d.ts.map +1 -1
- package/dist/agent/envoy-client.js +56 -6
- package/dist/agent/envoy-client.js.map +1 -1
- package/dist/agent/stale-deployment-detector.js +1 -1
- package/dist/agent/stale-deployment-detector.js.map +1 -1
- package/dist/agent/synth-agent.d.ts +7 -5
- package/dist/agent/synth-agent.d.ts.map +1 -1
- package/dist/agent/synth-agent.js +42 -39
- package/dist/agent/synth-agent.js.map +1 -1
- package/dist/alert-webhooks/alert-parsers.d.ts +21 -0
- package/dist/alert-webhooks/alert-parsers.d.ts.map +1 -0
- package/dist/alert-webhooks/alert-parsers.js +184 -0
- package/dist/alert-webhooks/alert-parsers.js.map +1 -0
- package/dist/api/agent.d.ts +0 -6
- package/dist/api/agent.d.ts.map +1 -1
- package/dist/api/agent.js +6 -459
- package/dist/api/agent.js.map +1 -1
- package/dist/api/alert-webhooks.d.ts +13 -0
- package/dist/api/alert-webhooks.d.ts.map +1 -0
- package/dist/api/alert-webhooks.js +279 -0
- package/dist/api/alert-webhooks.js.map +1 -0
- package/dist/api/envoy-reports.js +2 -2
- package/dist/api/envoy-reports.js.map +1 -1
- package/dist/api/envoys.js +1 -1
- package/dist/api/envoys.js.map +1 -1
- package/dist/api/fleet.d.ts.map +1 -1
- package/dist/api/fleet.js +14 -15
- package/dist/api/fleet.js.map +1 -1
- package/dist/api/graph.js +3 -3
- package/dist/api/graph.js.map +1 -1
- package/dist/api/operations.d.ts +7 -0
- package/dist/api/operations.d.ts.map +1 -0
- package/dist/api/operations.js +1883 -0
- package/dist/api/operations.js.map +1 -0
- package/dist/api/partitions.js +1 -1
- package/dist/api/partitions.js.map +1 -1
- package/dist/api/schemas.d.ts +194 -10
- package/dist/api/schemas.d.ts.map +1 -1
- package/dist/api/schemas.js +38 -5
- package/dist/api/schemas.js.map +1 -1
- package/dist/api/system.d.ts.map +1 -1
- package/dist/api/system.js +22 -21
- package/dist/api/system.js.map +1 -1
- package/dist/artifact-analyzer.js +2 -2
- package/dist/artifact-analyzer.js.map +1 -1
- package/dist/fleet/fleet-executor.js +1 -1
- package/dist/fleet/fleet-executor.js.map +1 -1
- package/dist/graph/graph-executor.js +2 -2
- package/dist/graph/graph-executor.js.map +1 -1
- package/dist/index.js +44 -40
- package/dist/index.js.map +1 -1
- package/dist/mcp/resources.js +3 -3
- package/dist/mcp/resources.js.map +1 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +2 -9
- package/dist/mcp/tools.js.map +1 -1
- package/dist/middleware/auth.js +1 -1
- package/dist/middleware/auth.js.map +1 -1
- package/package.json +1 -1
- package/src/agent/envoy-client.ts +107 -15
- package/src/agent/stale-deployment-detector.ts +1 -1
- package/src/agent/synth-agent.ts +59 -45
- package/src/alert-webhooks/alert-parsers.ts +291 -0
- package/src/api/agent.ts +9 -528
- package/src/api/alert-webhooks.ts +354 -0
- package/src/api/envoy-reports.ts +2 -2
- package/src/api/envoys.ts +1 -1
- package/src/api/fleet.ts +14 -15
- package/src/api/graph.ts +3 -3
- package/src/api/operations.ts +2240 -0
- package/src/api/partitions.ts +1 -1
- package/src/api/schemas.ts +43 -7
- package/src/api/system.ts +23 -21
- package/src/artifact-analyzer.ts +2 -2
- package/src/fleet/fleet-executor.ts +1 -1
- package/src/graph/graph-executor.ts +2 -2
- package/src/index.ts +46 -40
- package/src/mcp/resources.ts +3 -3
- package/src/mcp/tools.ts +5 -9
- package/src/middleware/auth.ts +1 -1
- package/tests/agent-mode.test.ts +5 -376
- package/tests/api-handlers.test.ts +27 -27
- package/tests/composite-operations.test.ts +557 -0
- package/tests/decision-diary.test.ts +62 -63
- package/tests/diary-reader.test.ts +14 -18
- package/tests/mcp-tools.test.ts +1 -1
- package/tests/orchestration.test.ts +34 -30
- package/tests/partition-isolation.test.ts +4 -9
- package/tests/rbac-enforcement.test.ts +8 -8
- package/tests/ui-journey.test.ts +9 -9
- package/dist/api/deployments.d.ts +0 -11
- package/dist/api/deployments.d.ts.map +0 -1
- package/dist/api/deployments.js +0 -1098
- package/dist/api/deployments.js.map +0 -1
- package/src/api/deployments.ts +0 -1347
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alert payload parsers for external monitoring system webhooks.
|
|
3
|
+
*
|
|
4
|
+
* Each parser normalizes the raw webhook body from a specific alerting
|
|
5
|
+
* platform into a common NormalizedAlert structure that Synth uses to
|
|
6
|
+
* spawn operations.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { NormalizedAlert, AlertWebhookSource } from "@synth-deploy/core";
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Prometheus AlertManager
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
interface PrometheusAlert {
|
|
16
|
+
status: "firing" | "resolved";
|
|
17
|
+
labels: Record<string, string>;
|
|
18
|
+
annotations: Record<string, string>;
|
|
19
|
+
startsAt?: string;
|
|
20
|
+
endsAt?: string;
|
|
21
|
+
generatorURL?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface PrometheusPayload {
|
|
25
|
+
version?: string;
|
|
26
|
+
status?: string;
|
|
27
|
+
alerts?: PrometheusAlert[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function parsePrometheusAlerts(body: unknown): NormalizedAlert[] {
|
|
31
|
+
const payload = body as PrometheusPayload;
|
|
32
|
+
if (!payload?.alerts || !Array.isArray(payload.alerts)) return [];
|
|
33
|
+
|
|
34
|
+
return payload.alerts
|
|
35
|
+
.filter((a) => a.status === "firing")
|
|
36
|
+
.map((alert): NormalizedAlert => ({
|
|
37
|
+
name: alert.labels?.alertname ?? "Unknown",
|
|
38
|
+
summary: alert.annotations?.summary ?? alert.annotations?.description ?? alert.labels?.alertname ?? "Alert fired",
|
|
39
|
+
severity: normalizeSeverity(alert.labels?.severity),
|
|
40
|
+
status: alert.status,
|
|
41
|
+
labels: alert.labels ?? {},
|
|
42
|
+
annotations: alert.annotations ?? {},
|
|
43
|
+
source: "prometheus",
|
|
44
|
+
startsAt: alert.startsAt ? new Date(alert.startsAt) : undefined,
|
|
45
|
+
rawPayload: alert as unknown as Record<string, unknown>,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// PagerDuty (v2 webhook / Events API v2)
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
interface PagerDutyMessage {
|
|
54
|
+
event?: string;
|
|
55
|
+
incident?: {
|
|
56
|
+
title?: string;
|
|
57
|
+
urgency?: string;
|
|
58
|
+
status?: string;
|
|
59
|
+
service?: { name?: string };
|
|
60
|
+
created_at?: string;
|
|
61
|
+
[key: string]: unknown;
|
|
62
|
+
};
|
|
63
|
+
[key: string]: unknown;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface PagerDutyPayload {
|
|
67
|
+
messages?: PagerDutyMessage[];
|
|
68
|
+
event?: { event_type?: string; data?: Record<string, unknown> };
|
|
69
|
+
[key: string]: unknown;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function parsePagerDutyAlerts(body: unknown): NormalizedAlert[] {
|
|
73
|
+
const payload = body as PagerDutyPayload;
|
|
74
|
+
|
|
75
|
+
// V3 webhook format (event.event_type / event.data)
|
|
76
|
+
if (payload?.event?.event_type) {
|
|
77
|
+
const evt = payload.event;
|
|
78
|
+
const data = (evt.data ?? {}) as Record<string, unknown>;
|
|
79
|
+
const isFiring = String(evt.event_type).includes("triggered");
|
|
80
|
+
if (!isFiring) return [];
|
|
81
|
+
|
|
82
|
+
return [{
|
|
83
|
+
name: String(data.title ?? "PagerDuty Alert"),
|
|
84
|
+
summary: String(data.title ?? evt.event_type),
|
|
85
|
+
severity: normalizeSeverity(String(data.urgency ?? data.priority ?? "warning")),
|
|
86
|
+
status: "firing",
|
|
87
|
+
labels: { service: String((data.service as Record<string, unknown>)?.name ?? "unknown") },
|
|
88
|
+
annotations: {},
|
|
89
|
+
source: "pagerduty",
|
|
90
|
+
startsAt: data.created_at ? new Date(String(data.created_at)) : undefined,
|
|
91
|
+
rawPayload: payload as Record<string, unknown>,
|
|
92
|
+
}];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// V2 webhook format (messages array)
|
|
96
|
+
if (!payload?.messages || !Array.isArray(payload.messages)) return [];
|
|
97
|
+
|
|
98
|
+
return payload.messages
|
|
99
|
+
.filter((m) => m.event === "incident.trigger")
|
|
100
|
+
.map((msg): NormalizedAlert => {
|
|
101
|
+
const inc = msg.incident ?? {};
|
|
102
|
+
return {
|
|
103
|
+
name: String(inc.title ?? "PagerDuty Incident"),
|
|
104
|
+
summary: String(inc.title ?? "Incident triggered"),
|
|
105
|
+
severity: normalizeSeverity(String(inc.urgency ?? "warning")),
|
|
106
|
+
status: "firing",
|
|
107
|
+
labels: { service: String(inc.service?.name ?? "unknown") },
|
|
108
|
+
annotations: {},
|
|
109
|
+
source: "pagerduty",
|
|
110
|
+
startsAt: inc.created_at ? new Date(String(inc.created_at)) : undefined,
|
|
111
|
+
rawPayload: msg as unknown as Record<string, unknown>,
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Datadog
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
interface DatadogPayload {
|
|
121
|
+
title?: string;
|
|
122
|
+
text?: string;
|
|
123
|
+
alert_type?: string;
|
|
124
|
+
priority?: string;
|
|
125
|
+
tags?: string[];
|
|
126
|
+
date?: number;
|
|
127
|
+
event_type?: string;
|
|
128
|
+
alert_transition?: string;
|
|
129
|
+
[key: string]: unknown;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function parseDatadogAlerts(body: unknown): NormalizedAlert[] {
|
|
133
|
+
const payload = body as DatadogPayload;
|
|
134
|
+
if (!payload?.title) return [];
|
|
135
|
+
|
|
136
|
+
// Only fire on "Triggered" transitions (not "Recovered" or "No Data")
|
|
137
|
+
const transition = String(payload.alert_transition ?? "Triggered");
|
|
138
|
+
if (transition !== "Triggered") return [];
|
|
139
|
+
|
|
140
|
+
const labels: Record<string, string> = {};
|
|
141
|
+
if (payload.tags) {
|
|
142
|
+
for (const tag of payload.tags) {
|
|
143
|
+
const [k, ...v] = tag.split(":");
|
|
144
|
+
labels[k] = v.join(":") || "true";
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return [{
|
|
149
|
+
name: payload.title,
|
|
150
|
+
summary: payload.text ?? payload.title,
|
|
151
|
+
severity: normalizeSeverity(payload.alert_type ?? payload.priority ?? "warning"),
|
|
152
|
+
status: "firing",
|
|
153
|
+
labels,
|
|
154
|
+
annotations: {},
|
|
155
|
+
source: "datadog",
|
|
156
|
+
startsAt: payload.date ? new Date(payload.date * 1000) : undefined,
|
|
157
|
+
rawPayload: payload as Record<string, unknown>,
|
|
158
|
+
}];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Grafana (Unified Alerting webhook)
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
interface GrafanaAlert {
|
|
166
|
+
status?: string;
|
|
167
|
+
labels?: Record<string, string>;
|
|
168
|
+
annotations?: Record<string, string>;
|
|
169
|
+
startsAt?: string;
|
|
170
|
+
endsAt?: string;
|
|
171
|
+
[key: string]: unknown;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
interface GrafanaPayload {
|
|
175
|
+
status?: string;
|
|
176
|
+
alerts?: GrafanaAlert[];
|
|
177
|
+
title?: string;
|
|
178
|
+
message?: string;
|
|
179
|
+
[key: string]: unknown;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function parseGrafanaAlerts(body: unknown): NormalizedAlert[] {
|
|
183
|
+
const payload = body as GrafanaPayload;
|
|
184
|
+
|
|
185
|
+
// Unified Alerting format (same shape as Prometheus)
|
|
186
|
+
if (payload?.alerts && Array.isArray(payload.alerts)) {
|
|
187
|
+
return payload.alerts
|
|
188
|
+
.filter((a) => (a.status ?? "firing") === "firing")
|
|
189
|
+
.map((alert): NormalizedAlert => ({
|
|
190
|
+
name: alert.labels?.alertname ?? payload.title ?? "Grafana Alert",
|
|
191
|
+
summary: alert.annotations?.summary ?? alert.annotations?.description ?? payload.message ?? "Alert fired",
|
|
192
|
+
severity: normalizeSeverity(alert.labels?.severity),
|
|
193
|
+
status: "firing",
|
|
194
|
+
labels: alert.labels ?? {},
|
|
195
|
+
annotations: alert.annotations ?? {},
|
|
196
|
+
source: "grafana",
|
|
197
|
+
startsAt: alert.startsAt ? new Date(alert.startsAt) : undefined,
|
|
198
|
+
rawPayload: alert as unknown as Record<string, unknown>,
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Legacy notification format (single alert in body)
|
|
203
|
+
if (payload?.title) {
|
|
204
|
+
return [{
|
|
205
|
+
name: payload.title,
|
|
206
|
+
summary: String(payload.message ?? payload.title),
|
|
207
|
+
severity: normalizeSeverity(String(payload.status ?? "warning")),
|
|
208
|
+
status: payload.status === "resolved" ? "resolved" : "firing",
|
|
209
|
+
labels: {},
|
|
210
|
+
annotations: {},
|
|
211
|
+
source: "grafana",
|
|
212
|
+
rawPayload: payload as Record<string, unknown>,
|
|
213
|
+
}];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
// Generic — accepts a simple JSON shape for custom integrations
|
|
221
|
+
// ---------------------------------------------------------------------------
|
|
222
|
+
|
|
223
|
+
interface GenericPayload {
|
|
224
|
+
name?: string;
|
|
225
|
+
summary?: string;
|
|
226
|
+
severity?: string;
|
|
227
|
+
status?: string;
|
|
228
|
+
labels?: Record<string, string>;
|
|
229
|
+
annotations?: Record<string, string>;
|
|
230
|
+
[key: string]: unknown;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function parseGenericAlerts(body: unknown): NormalizedAlert[] {
|
|
234
|
+
const payload = body as GenericPayload;
|
|
235
|
+
if (!payload || typeof payload !== "object") return [];
|
|
236
|
+
|
|
237
|
+
const status = (payload.status ?? "firing") === "resolved" ? "resolved" as const : "firing" as const;
|
|
238
|
+
if (status === "resolved") return [];
|
|
239
|
+
|
|
240
|
+
return [{
|
|
241
|
+
name: String(payload.name ?? "External Alert"),
|
|
242
|
+
summary: String(payload.summary ?? payload.name ?? "Alert received"),
|
|
243
|
+
severity: normalizeSeverity(payload.severity),
|
|
244
|
+
status,
|
|
245
|
+
labels: payload.labels ?? {},
|
|
246
|
+
annotations: payload.annotations ?? {},
|
|
247
|
+
source: "generic",
|
|
248
|
+
rawPayload: payload as Record<string, unknown>,
|
|
249
|
+
}];
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
// Router — pick the right parser based on source
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
|
|
256
|
+
export function parseAlerts(source: AlertWebhookSource, body: unknown): NormalizedAlert[] {
|
|
257
|
+
switch (source) {
|
|
258
|
+
case "prometheus": return parsePrometheusAlerts(body);
|
|
259
|
+
case "pagerduty": return parsePagerDutyAlerts(body);
|
|
260
|
+
case "datadog": return parseDatadogAlerts(body);
|
|
261
|
+
case "grafana": return parseGrafanaAlerts(body);
|
|
262
|
+
case "generic": return parseGenericAlerts(body);
|
|
263
|
+
default: return parseGenericAlerts(body);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
// Helpers
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
|
|
271
|
+
function normalizeSeverity(raw?: string): "critical" | "warning" | "info" {
|
|
272
|
+
if (!raw) return "warning";
|
|
273
|
+
const lower = raw.toLowerCase();
|
|
274
|
+
if (lower === "critical" || lower === "error" || lower === "high" || lower === "p1") return "critical";
|
|
275
|
+
if (lower === "info" || lower === "low" || lower === "p3" || lower === "p4" || lower === "ok") return "info";
|
|
276
|
+
return "warning";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Interpolate alert fields into an intent template.
|
|
281
|
+
* Supported placeholders: {{alert.name}}, {{alert.summary}}, {{alert.severity}},
|
|
282
|
+
* {{alert.labels.<key>}}, {{alert.annotations.<key>}}.
|
|
283
|
+
*/
|
|
284
|
+
export function interpolateIntent(template: string, alert: NormalizedAlert): string {
|
|
285
|
+
return template
|
|
286
|
+
.replace(/\{\{alert\.name\}\}/g, alert.name)
|
|
287
|
+
.replace(/\{\{alert\.summary\}\}/g, alert.summary)
|
|
288
|
+
.replace(/\{\{alert\.severity\}\}/g, alert.severity)
|
|
289
|
+
.replace(/\{\{alert\.labels\.(\w+)\}\}/g, (_, key) => alert.labels[key] ?? "")
|
|
290
|
+
.replace(/\{\{alert\.annotations\.(\w+)\}\}/g, (_, key) => alert.annotations[key] ?? "");
|
|
291
|
+
}
|