@synth-deploy/server 1.0.6 → 1.2.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.
Files changed (97) hide show
  1. package/dist/agent/envoy-client.d.ts +65 -15
  2. package/dist/agent/envoy-client.d.ts.map +1 -1
  3. package/dist/agent/envoy-client.js +58 -8
  4. package/dist/agent/envoy-client.js.map +1 -1
  5. package/dist/agent/stale-deployment-detector.js +1 -1
  6. package/dist/agent/stale-deployment-detector.js.map +1 -1
  7. package/dist/agent/synth-agent.d.ts +7 -5
  8. package/dist/agent/synth-agent.d.ts.map +1 -1
  9. package/dist/agent/synth-agent.js +59 -50
  10. package/dist/agent/synth-agent.js.map +1 -1
  11. package/dist/alert-webhooks/alert-parsers.d.ts +21 -0
  12. package/dist/alert-webhooks/alert-parsers.d.ts.map +1 -0
  13. package/dist/alert-webhooks/alert-parsers.js +184 -0
  14. package/dist/alert-webhooks/alert-parsers.js.map +1 -0
  15. package/dist/api/agent.d.ts +0 -6
  16. package/dist/api/agent.d.ts.map +1 -1
  17. package/dist/api/agent.js +6 -459
  18. package/dist/api/agent.js.map +1 -1
  19. package/dist/api/alert-webhooks.d.ts +13 -0
  20. package/dist/api/alert-webhooks.d.ts.map +1 -0
  21. package/dist/api/alert-webhooks.js +279 -0
  22. package/dist/api/alert-webhooks.js.map +1 -0
  23. package/dist/api/envoy-reports.js +2 -2
  24. package/dist/api/envoy-reports.js.map +1 -1
  25. package/dist/api/envoys.js +1 -1
  26. package/dist/api/envoys.js.map +1 -1
  27. package/dist/api/fleet.d.ts.map +1 -1
  28. package/dist/api/fleet.js +14 -15
  29. package/dist/api/fleet.js.map +1 -1
  30. package/dist/api/graph.js +3 -3
  31. package/dist/api/graph.js.map +1 -1
  32. package/dist/api/operations.d.ts +7 -0
  33. package/dist/api/operations.d.ts.map +1 -0
  34. package/dist/api/operations.js +1900 -0
  35. package/dist/api/operations.js.map +1 -0
  36. package/dist/api/partitions.js +1 -1
  37. package/dist/api/partitions.js.map +1 -1
  38. package/dist/api/schemas.d.ts +434 -133
  39. package/dist/api/schemas.d.ts.map +1 -1
  40. package/dist/api/schemas.js +53 -25
  41. package/dist/api/schemas.js.map +1 -1
  42. package/dist/api/system.d.ts.map +1 -1
  43. package/dist/api/system.js +22 -21
  44. package/dist/api/system.js.map +1 -1
  45. package/dist/artifact-analyzer.js +2 -2
  46. package/dist/artifact-analyzer.js.map +1 -1
  47. package/dist/fleet/fleet-executor.js +3 -3
  48. package/dist/fleet/fleet-executor.js.map +1 -1
  49. package/dist/graph/graph-executor.d.ts.map +1 -1
  50. package/dist/graph/graph-executor.js +18 -4
  51. package/dist/graph/graph-executor.js.map +1 -1
  52. package/dist/index.js +89 -61
  53. package/dist/index.js.map +1 -1
  54. package/dist/mcp/resources.js +3 -3
  55. package/dist/mcp/resources.js.map +1 -1
  56. package/dist/mcp/tools.d.ts.map +1 -1
  57. package/dist/mcp/tools.js +2 -9
  58. package/dist/mcp/tools.js.map +1 -1
  59. package/dist/middleware/auth.js +1 -1
  60. package/dist/middleware/auth.js.map +1 -1
  61. package/package.json +1 -1
  62. package/src/agent/envoy-client.ts +111 -19
  63. package/src/agent/stale-deployment-detector.ts +1 -1
  64. package/src/agent/synth-agent.ts +76 -56
  65. package/src/alert-webhooks/alert-parsers.ts +291 -0
  66. package/src/api/agent.ts +9 -528
  67. package/src/api/alert-webhooks.ts +354 -0
  68. package/src/api/envoy-reports.ts +2 -2
  69. package/src/api/envoys.ts +1 -1
  70. package/src/api/fleet.ts +14 -15
  71. package/src/api/graph.ts +3 -3
  72. package/src/api/operations.ts +2260 -0
  73. package/src/api/partitions.ts +1 -1
  74. package/src/api/schemas.ts +59 -27
  75. package/src/api/system.ts +23 -21
  76. package/src/artifact-analyzer.ts +2 -2
  77. package/src/fleet/fleet-executor.ts +3 -3
  78. package/src/graph/graph-executor.ts +18 -4
  79. package/src/index.ts +91 -61
  80. package/src/mcp/resources.ts +3 -3
  81. package/src/mcp/tools.ts +5 -9
  82. package/src/middleware/auth.ts +1 -1
  83. package/tests/agent-mode.test.ts +5 -376
  84. package/tests/api-handlers.test.ts +27 -27
  85. package/tests/composite-operations.test.ts +557 -0
  86. package/tests/decision-diary.test.ts +62 -63
  87. package/tests/diary-reader.test.ts +14 -18
  88. package/tests/mcp-tools.test.ts +1 -1
  89. package/tests/orchestration.test.ts +34 -30
  90. package/tests/partition-isolation.test.ts +4 -9
  91. package/tests/rbac-enforcement.test.ts +8 -8
  92. package/tests/ui-journey.test.ts +9 -9
  93. package/dist/api/deployments.d.ts +0 -11
  94. package/dist/api/deployments.d.ts.map +0 -1
  95. package/dist/api/deployments.js +0 -1098
  96. package/dist/api/deployments.js.map +0 -1
  97. 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
+ }