@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
package/src/api/partitions.ts
CHANGED
|
@@ -85,7 +85,7 @@ export function registerPartitionRoutes(
|
|
|
85
85
|
// Log cascade deletion to Decision Diary
|
|
86
86
|
debrief.record({
|
|
87
87
|
partitionId: id,
|
|
88
|
-
|
|
88
|
+
operationId: null,
|
|
89
89
|
agent: "server",
|
|
90
90
|
decisionType: "system",
|
|
91
91
|
decision: `Cascade-deleted partition "${partition.name}" with ${linkedDeployments.length} deployment(s)`,
|
package/src/api/schemas.ts
CHANGED
|
@@ -211,6 +211,17 @@ export const UpdateSettingsSchema = z.object({
|
|
|
211
211
|
description: z.string().optional(),
|
|
212
212
|
})).optional(),
|
|
213
213
|
llm: LlmProviderConfigSchema.partial().optional(),
|
|
214
|
+
approvalDefaults: z.object({
|
|
215
|
+
query: z.enum(["auto", "required"]).optional(),
|
|
216
|
+
investigate: z.enum(["auto", "required"]).optional(),
|
|
217
|
+
trigger: z.enum(["auto", "required"]).optional(),
|
|
218
|
+
deploy: z.enum(["auto", "required"]).optional(),
|
|
219
|
+
maintain: z.enum(["auto", "required"]).optional(),
|
|
220
|
+
composite: z.enum(["auto", "required"]).optional(),
|
|
221
|
+
environmentOverrides: z.record(
|
|
222
|
+
z.record(z.enum(["auto", "required"])).optional(),
|
|
223
|
+
).optional(),
|
|
224
|
+
}).optional(),
|
|
214
225
|
});
|
|
215
226
|
|
|
216
227
|
// --- Artifacts (update) ---
|
|
@@ -232,6 +243,37 @@ export const CreateDeploymentSchema = z.object({
|
|
|
232
243
|
version: z.string().optional(),
|
|
233
244
|
});
|
|
234
245
|
|
|
246
|
+
const ChildOperationInputSchema = z.discriminatedUnion("type", [
|
|
247
|
+
z.object({ type: z.literal("deploy"), artifactId: z.string().min(1), artifactVersionId: z.string().optional() }),
|
|
248
|
+
z.object({ type: z.literal("maintain"), intent: z.string().min(1), parameters: z.record(z.unknown()).optional() }),
|
|
249
|
+
z.object({ type: z.literal("query"), intent: z.string().min(1) }),
|
|
250
|
+
z.object({ type: z.literal("investigate"), intent: z.string().min(1), allowWrite: z.boolean().optional() }),
|
|
251
|
+
z.object({ type: z.literal("trigger"), condition: z.string().min(1), responseIntent: z.string().min(1), parameters: z.record(z.unknown()).optional() }),
|
|
252
|
+
]);
|
|
253
|
+
|
|
254
|
+
// --- Operations ---
|
|
255
|
+
|
|
256
|
+
export const CreateOperationSchema = z.object({
|
|
257
|
+
artifactId: z.string().min(1).optional(),
|
|
258
|
+
environmentId: z.string().min(1).optional(),
|
|
259
|
+
partitionId: z.string().optional(),
|
|
260
|
+
envoyId: z.string().optional(),
|
|
261
|
+
version: z.string().optional(),
|
|
262
|
+
type: z.enum(["deploy", "maintain", "query", "investigate", "trigger", "composite"]).default("deploy"),
|
|
263
|
+
intent: z.string().optional(),
|
|
264
|
+
allowWrite: z.boolean().optional(),
|
|
265
|
+
/** Trigger-specific: condition expression (e.g. "disk_usage > 85") */
|
|
266
|
+
condition: z.string().optional(),
|
|
267
|
+
/** Trigger-specific: what to do when the condition fires */
|
|
268
|
+
responseIntent: z.string().optional(),
|
|
269
|
+
/** Parent operation that spawned this one (e.g. investigation → resolution) */
|
|
270
|
+
parentOperationId: z.string().optional(),
|
|
271
|
+
/** Override to require manual approval even when auto-approve would apply */
|
|
272
|
+
requireApproval: z.boolean().optional(),
|
|
273
|
+
/** Composite-specific: child operations to execute sequentially */
|
|
274
|
+
operations: z.array(ChildOperationInputSchema).optional(),
|
|
275
|
+
});
|
|
276
|
+
|
|
235
277
|
export const ApproveDeploymentSchema = z.object({
|
|
236
278
|
approvedBy: z.string().min(1),
|
|
237
279
|
modifications: z.string().optional(),
|
|
@@ -291,6 +333,7 @@ export const DebriefQuerySchema = z.object({
|
|
|
291
333
|
limit: z.coerce.number().int().positive().optional(),
|
|
292
334
|
partitionId: z.string().optional(),
|
|
293
335
|
decisionType: z.string().optional(),
|
|
336
|
+
q: z.string().optional(),
|
|
294
337
|
});
|
|
295
338
|
|
|
296
339
|
// --- Progress Events (from envoy callback) ---
|
|
@@ -325,13 +368,6 @@ export const TelemetryQuerySchema = z.object({
|
|
|
325
368
|
offset: z.coerce.number().int().nonnegative().optional(),
|
|
326
369
|
});
|
|
327
370
|
|
|
328
|
-
// --- Agent ---
|
|
329
|
-
|
|
330
|
-
export const QueryRequestSchema = z.object({
|
|
331
|
-
query: z.string().min(1),
|
|
332
|
-
conversationId: z.string().optional(),
|
|
333
|
-
});
|
|
334
|
-
|
|
335
371
|
// --- Auth ---
|
|
336
372
|
|
|
337
373
|
export const LoginSchema = z.object({
|
package/src/api/system.ts
CHANGED
|
@@ -156,6 +156,8 @@ export function registerSystemRoutes(
|
|
|
156
156
|
const allEnvoys = envoyRegistry.list();
|
|
157
157
|
const allDeployments = deployments.list();
|
|
158
158
|
const allEnvironments = environments.list();
|
|
159
|
+
const getArtifactId = (op: (typeof allDeployments)[number]): string =>
|
|
160
|
+
op.input?.type === "deploy" ? (op.input as { type: "deploy"; artifactId: string }).artifactId : "";
|
|
159
161
|
const allPartitions = partitions.list();
|
|
160
162
|
|
|
161
163
|
// --- Stats ---
|
|
@@ -264,11 +266,11 @@ export function registerSystemRoutes(
|
|
|
264
266
|
{ time: nowTime(), event: "Signal raised" },
|
|
265
267
|
],
|
|
266
268
|
relatedDeployments: recentToEnvoy.map((d) => {
|
|
267
|
-
const artName = allArtifacts.find((a) => a.id === d
|
|
269
|
+
const artName = allArtifacts.find((a) => a.id === getArtifactId(d))?.name ?? getArtifactId(d).slice(0, 8);
|
|
268
270
|
const envName = allEnvironments.find((e) => e.id === d.environmentId)?.name ?? "unknown";
|
|
269
271
|
return {
|
|
270
272
|
artifact: artName,
|
|
271
|
-
version: d.version,
|
|
273
|
+
version: d.version ?? "",
|
|
272
274
|
target: envName,
|
|
273
275
|
status: d.status,
|
|
274
276
|
time: timeAgo(d.createdAt),
|
|
@@ -291,9 +293,9 @@ export function registerSystemRoutes(
|
|
|
291
293
|
type FailureGroup = { artifactId: string; environmentId: string | undefined; failures: typeof recentFailures };
|
|
292
294
|
const failureGroups = new Map<string, FailureGroup>();
|
|
293
295
|
for (const dep of recentFailures) {
|
|
294
|
-
const key = `${dep
|
|
296
|
+
const key = `${getArtifactId(dep)}::${dep.environmentId}`;
|
|
295
297
|
if (!failureGroups.has(key)) {
|
|
296
|
-
failureGroups.set(key, { artifactId: dep
|
|
298
|
+
failureGroups.set(key, { artifactId: getArtifactId(dep), environmentId: dep.environmentId, failures: [] });
|
|
297
299
|
}
|
|
298
300
|
failureGroups.get(key)!.failures.push(dep);
|
|
299
301
|
}
|
|
@@ -303,7 +305,7 @@ export function registerSystemRoutes(
|
|
|
303
305
|
|
|
304
306
|
const hasRecovery = allDeployments.some(
|
|
305
307
|
(d) =>
|
|
306
|
-
d
|
|
308
|
+
getArtifactId(d) === group.artifactId &&
|
|
307
309
|
d.environmentId === group.environmentId &&
|
|
308
310
|
d.status === "succeeded" &&
|
|
309
311
|
new Date(d.createdAt).getTime() > new Date(group.failures[0].createdAt).getTime(),
|
|
@@ -320,7 +322,7 @@ export function registerSystemRoutes(
|
|
|
320
322
|
const prevSuccessful = allDeployments
|
|
321
323
|
.filter(
|
|
322
324
|
(d) =>
|
|
323
|
-
d
|
|
325
|
+
getArtifactId(d) === group.artifactId &&
|
|
324
326
|
d.environmentId === group.environmentId &&
|
|
325
327
|
d.status === "succeeded",
|
|
326
328
|
)
|
|
@@ -379,7 +381,7 @@ export function registerSystemRoutes(
|
|
|
379
381
|
],
|
|
380
382
|
relatedDeployments: sorted.map((d) => ({
|
|
381
383
|
artifact: artifactName,
|
|
382
|
-
version: d.version,
|
|
384
|
+
version: d.version ?? "",
|
|
383
385
|
target: envName,
|
|
384
386
|
status: d.status,
|
|
385
387
|
time: timeAgo(d.createdAt),
|
|
@@ -397,7 +399,7 @@ export function registerSystemRoutes(
|
|
|
397
399
|
type EnvLatest = { dep: (typeof succeededDeps)[0]; envName: string };
|
|
398
400
|
const latestByTarget = new Map<string, EnvLatest>();
|
|
399
401
|
for (const dep of succeededDeps) {
|
|
400
|
-
const key = `${dep
|
|
402
|
+
const key = `${getArtifactId(dep)}::${dep.environmentId}`;
|
|
401
403
|
const existing = latestByTarget.get(key);
|
|
402
404
|
if (!existing || new Date(dep.createdAt) > new Date(existing.dep.createdAt)) {
|
|
403
405
|
const envName = allEnvironments.find((e) => e.id === dep.environmentId)?.name ?? "unknown";
|
|
@@ -408,13 +410,13 @@ export function registerSystemRoutes(
|
|
|
408
410
|
for (const { dep, envName } of latestByTarget.values()) {
|
|
409
411
|
if (new Date(dep.createdAt).getTime() > thirtyDaysAgo) continue; // Not stale yet
|
|
410
412
|
|
|
411
|
-
const artifactName = allArtifacts.find((a) => a.id === dep
|
|
413
|
+
const artifactName = allArtifacts.find((a) => a.id === getArtifactId(dep))?.name ?? "unknown";
|
|
412
414
|
const weeksAgo = Math.floor((now - new Date(dep.createdAt).getTime()) / (7 * 24 * 60 * 60 * 1000));
|
|
413
415
|
|
|
414
416
|
// Check if newer versions of this artifact have been deployed to any other environment
|
|
415
417
|
const newerElsewhere = succeededDeps.filter(
|
|
416
418
|
(d) =>
|
|
417
|
-
d
|
|
419
|
+
getArtifactId(d) === getArtifactId(dep) &&
|
|
418
420
|
d.environmentId !== dep.environmentId &&
|
|
419
421
|
new Date(d.createdAt).getTime() > new Date(dep.createdAt).getTime(),
|
|
420
422
|
);
|
|
@@ -427,7 +429,7 @@ export function registerSystemRoutes(
|
|
|
427
429
|
severity: "info",
|
|
428
430
|
title: `Stale deployment: ${artifactName} in ${envName}`,
|
|
429
431
|
detail: `v${dep.version} deployed ${weeksAgo}w ago. ${newerVersions.length} newer version${newerVersions.length > 1 ? "s" : ""} running elsewhere. May be intentional.`,
|
|
430
|
-
relatedEntity: { type: "artifact", id: dep
|
|
432
|
+
relatedEntity: { type: "artifact", id: getArtifactId(dep), name: artifactName },
|
|
431
433
|
investigation: {
|
|
432
434
|
title: `Stale Deployment — ${artifactName} in ${envName}`,
|
|
433
435
|
entity: `${artifactName} in ${envName}`,
|
|
@@ -461,10 +463,10 @@ export function registerSystemRoutes(
|
|
|
461
463
|
{ time: nowTime(), event: `Signal raised — ${weeksAgo}w without update, newer versions exist` },
|
|
462
464
|
],
|
|
463
465
|
relatedDeployments: [
|
|
464
|
-
{ artifact: artifactName, version: dep.version, target: envName, status: "succeeded", time: timeAgo(dep.createdAt) },
|
|
466
|
+
{ artifact: artifactName, version: dep.version ?? "", target: envName, status: "succeeded", time: timeAgo(dep.createdAt) },
|
|
465
467
|
...newerElsewhere.slice(0, 3).map((d) => {
|
|
466
468
|
const env = allEnvironments.find((e) => e.id === d.environmentId)?.name ?? "unknown";
|
|
467
|
-
return { artifact: artifactName, version: d.version, target: env, status: d.status, time: timeAgo(d.createdAt) };
|
|
469
|
+
return { artifact: artifactName, version: d.version ?? "", target: env, status: d.status, time: timeAgo(d.createdAt) };
|
|
468
470
|
}),
|
|
469
471
|
],
|
|
470
472
|
},
|
|
@@ -475,11 +477,11 @@ export function registerSystemRoutes(
|
|
|
475
477
|
// pattern that suggests a missed or skipped promotion.
|
|
476
478
|
const artifactEnvVersions = new Map<string, Map<string, { version: string; deployedAt: Date }>>();
|
|
477
479
|
for (const { dep, envName } of latestByTarget.values()) {
|
|
478
|
-
if (!artifactEnvVersions.has(dep
|
|
479
|
-
artifactEnvVersions.set(dep
|
|
480
|
+
if (!artifactEnvVersions.has(getArtifactId(dep))) {
|
|
481
|
+
artifactEnvVersions.set(getArtifactId(dep), new Map());
|
|
480
482
|
}
|
|
481
|
-
artifactEnvVersions.get(dep
|
|
482
|
-
version: dep.version,
|
|
483
|
+
artifactEnvVersions.get(getArtifactId(dep))!.set(envName, {
|
|
484
|
+
version: dep.version ?? "",
|
|
483
485
|
deployedAt: new Date(dep.createdAt),
|
|
484
486
|
});
|
|
485
487
|
}
|
|
@@ -503,7 +505,7 @@ export function registerSystemRoutes(
|
|
|
503
505
|
|
|
504
506
|
// Also require that the ahead env has more recent deployments of this artifact (not just same artifact)
|
|
505
507
|
const aheadHasMultiple = succeededDeps.filter(
|
|
506
|
-
(d) => d
|
|
508
|
+
(d) => getArtifactId(d) === artifactId &&
|
|
507
509
|
allEnvironments.find((e) => e.id === d.environmentId)?.name === aheadEnv,
|
|
508
510
|
).length >= 2;
|
|
509
511
|
if (!aheadHasMultiple) continue;
|
|
@@ -620,10 +622,10 @@ export function registerSystemRoutes(
|
|
|
620
622
|
{ time: nowTime(), event: "Signal raised" },
|
|
621
623
|
],
|
|
622
624
|
relatedDeployments: recentToEnv.map((d) => {
|
|
623
|
-
const artName = allArtifacts.find((a) => a.id === d
|
|
625
|
+
const artName = allArtifacts.find((a) => a.id === getArtifactId(d))?.name ?? getArtifactId(d).slice(0, 8);
|
|
624
626
|
return {
|
|
625
627
|
artifact: artName,
|
|
626
|
-
version: d.version,
|
|
628
|
+
version: d.version ?? "",
|
|
627
629
|
target: env.name,
|
|
628
630
|
status: d.status,
|
|
629
631
|
time: timeAgo(d.createdAt),
|
|
@@ -682,7 +684,7 @@ export function registerSystemRoutes(
|
|
|
682
684
|
detail = infos[0].detail;
|
|
683
685
|
} else if (activeDeployments.length > 0) {
|
|
684
686
|
const d = activeDeployments[0];
|
|
685
|
-
const artName = allArtifacts.find((a) => a.id === d
|
|
687
|
+
const artName = allArtifacts.find((a) => a.id === getArtifactId(d))?.name ?? "A deployment";
|
|
686
688
|
const envName = allEnvironments.find((e) => e.id === d.environmentId)?.name ?? "target";
|
|
687
689
|
headline = "Deployment in progress.";
|
|
688
690
|
detail = `${artName} is being deployed to ${envName}. All other environments are stable.`;
|
package/src/artifact-analyzer.ts
CHANGED
|
@@ -367,7 +367,7 @@ Produce a JSON analysis that incorporates all user corrections. Raise confidence
|
|
|
367
367
|
|
|
368
368
|
this._debrief.record({
|
|
369
369
|
partitionId: null,
|
|
370
|
-
|
|
370
|
+
operationId: null,
|
|
371
371
|
agent: "server",
|
|
372
372
|
decisionType: "artifact-analysis",
|
|
373
373
|
decision: `Re-analyzed "${artifact.name}" with ${artifact.annotations.length} user correction(s). Confidence: ${revised.confidence}.`,
|
|
@@ -406,7 +406,7 @@ Produce a JSON analysis that incorporates all user corrections. Raise confidence
|
|
|
406
406
|
|
|
407
407
|
this._debrief.record({
|
|
408
408
|
partitionId: null,
|
|
409
|
-
|
|
409
|
+
operationId: null,
|
|
410
410
|
agent: "server",
|
|
411
411
|
decisionType: "artifact-analysis",
|
|
412
412
|
decision: `Analyzed artifact "${artifact.name}" (${artifactType}) via ${method}. ` +
|
|
@@ -197,7 +197,7 @@ export class FleetExecutor {
|
|
|
197
197
|
const client = this.createEnvoyClient(entry.url, entry.token);
|
|
198
198
|
try {
|
|
199
199
|
await client.executeApprovedPlan({
|
|
200
|
-
|
|
200
|
+
operationId: fleetDeployment.id,
|
|
201
201
|
plan,
|
|
202
202
|
rollbackPlan: rollbackPlan ?? plan,
|
|
203
203
|
artifactType: "fleet",
|
|
@@ -243,7 +243,7 @@ export class GraphExecutor {
|
|
|
243
243
|
: plan;
|
|
244
244
|
|
|
245
245
|
const result: EnvoyDeployResult = await client.executeApprovedPlan({
|
|
246
|
-
|
|
246
|
+
operationId: node.deploymentId ?? node.id,
|
|
247
247
|
plan: enrichedPlan,
|
|
248
248
|
rollbackPlan: {
|
|
249
249
|
steps: [],
|
|
@@ -416,7 +416,7 @@ export class GraphExecutor {
|
|
|
416
416
|
|
|
417
417
|
try {
|
|
418
418
|
await client.executeApprovedPlan({
|
|
419
|
-
|
|
419
|
+
operationId: node.deploymentId ?? nodeId,
|
|
420
420
|
plan,
|
|
421
421
|
rollbackPlan: {
|
|
422
422
|
steps: [],
|
package/src/index.ts
CHANGED
|
@@ -9,13 +9,13 @@ import fastifyStatic from "@fastify/static";
|
|
|
9
9
|
import fastifyFormBody from "@fastify/formbody";
|
|
10
10
|
import fastifyMultipart from "@fastify/multipart";
|
|
11
11
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
12
|
-
import { PersistentDecisionDebrief, openEntityDatabase, PersistentPartitionStore, PersistentEnvironmentStore, PersistentSettingsStore, PersistentDeploymentStore, PersistentArtifactStore, PersistentSecurityBoundaryStore, PersistentTelemetryStore, PersistentUserStore, PersistentRoleStore, PersistentUserRoleStore, PersistentSessionStore, PersistentIdpProviderStore, PersistentRoleMappingStore, PersistentApiKeyStore, PersistentEnvoyRegistryStore, PersistentRegistryPollerVersionStore, LlmClient, buildLlmConfigFromSettings, initEdition, EditionError } from "@synth-deploy/core";
|
|
12
|
+
import { PersistentDecisionDebrief, openEntityDatabase, PersistentPartitionStore, PersistentEnvironmentStore, PersistentSettingsStore, PersistentDeploymentStore, PersistentArtifactStore, PersistentSecurityBoundaryStore, PersistentTelemetryStore, PersistentUserStore, PersistentRoleStore, PersistentUserRoleStore, PersistentSessionStore, PersistentIdpProviderStore, PersistentRoleMappingStore, PersistentApiKeyStore, PersistentEnvoyRegistryStore, PersistentRegistryPollerVersionStore, PersistentAlertWebhookStore, LlmClient, buildLlmConfigFromSettings, initEdition, EditionError } from "@synth-deploy/core";
|
|
13
13
|
import type { Deployment, Artifact, ArtifactVersion, SecurityBoundary, Permission, RoleId } from "@synth-deploy/core";
|
|
14
14
|
import { SynthAgent } from "./agent/synth-agent.js";
|
|
15
15
|
import { EnvoyHealthChecker } from "./agent/health-checker.js";
|
|
16
16
|
import { McpClientManager } from "./agent/mcp-client-manager.js";
|
|
17
17
|
import { createMcpServer } from "./mcp/server.js";
|
|
18
|
-
import {
|
|
18
|
+
import { registerOperationRoutes } from "./api/operations.js";
|
|
19
19
|
import { registerHealthRoutes } from "./api/health.js";
|
|
20
20
|
import { registerEnvoyReportRoutes } from "./api/envoy-reports.js";
|
|
21
21
|
import { registerArtifactRoutes } from "./api/artifacts.js";
|
|
@@ -41,6 +41,7 @@ import { registerFleetRoutes } from "./api/fleet.js";
|
|
|
41
41
|
import { FleetDeploymentStore, FleetExecutor } from "./fleet/index.js";
|
|
42
42
|
import { IntakeChannelStore, IntakeEventStore, IntakeProcessor, RegistryPoller } from "./intake/index.js";
|
|
43
43
|
import { registerIntakeRoutes } from "./api/intake.js";
|
|
44
|
+
import { registerAlertWebhookRoutes } from "./api/alert-webhooks.js";
|
|
44
45
|
import { ArtifactAnalyzer } from "./artifact-analyzer.js";
|
|
45
46
|
import { DeploymentGraphStore, GraphInferenceEngine } from "./graph/index.js";
|
|
46
47
|
import { registerGraphRoutes } from "./api/graph.js";
|
|
@@ -291,7 +292,7 @@ if (process.env.SYNTH_SEED_DEMO !== 'false' && partitions.list().length === 0) {
|
|
|
291
292
|
// --- Deployments (mix of statuses and ages) ---
|
|
292
293
|
|
|
293
294
|
const dep1: Deployment = {
|
|
294
|
-
id: crypto.randomUUID() as Deployment["id"], artifactId: webAppArtifact.id
|
|
295
|
+
id: crypto.randomUUID() as Deployment["id"], input: { type: "deploy" as const, artifactId: webAppArtifact.id }, partitionId: acmePartition.id as Deployment["partitionId"],
|
|
295
296
|
environmentId: prodEnv.id as Deployment["environmentId"], version: "2.3.0", status: "succeeded",
|
|
296
297
|
variables: { ...acmePartition.variables, ...prodEnv.variables },
|
|
297
298
|
plan: {
|
|
@@ -311,21 +312,21 @@ if (process.env.SYNTH_SEED_DEMO !== 'false' && partitions.list().length === 0) {
|
|
|
311
312
|
createdAt: hoursAgo(72), completedAt: hoursAgo(71.5), failureReason: undefined,
|
|
312
313
|
};
|
|
313
314
|
const dep2: Deployment = {
|
|
314
|
-
id: crypto.randomUUID() as Deployment["id"], artifactId: webAppArtifact.id
|
|
315
|
+
id: crypto.randomUUID() as Deployment["id"], input: { type: "deploy" as const, artifactId: webAppArtifact.id }, partitionId: acmePartition.id as Deployment["partitionId"],
|
|
315
316
|
environmentId: prodEnv.id as Deployment["environmentId"], version: "2.4.0", status: "succeeded",
|
|
316
317
|
variables: { ...acmePartition.variables, ...prodEnv.variables },
|
|
317
318
|
debriefEntryIds: [],
|
|
318
319
|
createdAt: hoursAgo(48), completedAt: hoursAgo(47.8), failureReason: undefined,
|
|
319
320
|
};
|
|
320
321
|
const dep3: Deployment = {
|
|
321
|
-
id: crypto.randomUUID() as Deployment["id"], artifactId: webAppArtifact.id
|
|
322
|
+
id: crypto.randomUUID() as Deployment["id"], input: { type: "deploy" as const, artifactId: webAppArtifact.id }, partitionId: acmePartition.id as Deployment["partitionId"],
|
|
322
323
|
environmentId: prodEnv.id as Deployment["environmentId"], version: "2.4.1", status: "succeeded",
|
|
323
324
|
variables: { ...acmePartition.variables, ...prodEnv.variables },
|
|
324
325
|
debriefEntryIds: [],
|
|
325
326
|
createdAt: hoursAgo(24), completedAt: hoursAgo(23.7), failureReason: undefined,
|
|
326
327
|
};
|
|
327
328
|
const dep4: Deployment = {
|
|
328
|
-
id: crypto.randomUUID() as Deployment["id"], artifactId: apiArtifact.id
|
|
329
|
+
id: crypto.randomUUID() as Deployment["id"], input: { type: "deploy" as const, artifactId: apiArtifact.id }, partitionId: acmePartition.id as Deployment["partitionId"],
|
|
329
330
|
environmentId: prodEnv.id as Deployment["environmentId"], version: "1.11.0", status: "failed",
|
|
330
331
|
variables: { ...acmePartition.variables, ...prodEnv.variables },
|
|
331
332
|
debriefEntryIds: [],
|
|
@@ -333,21 +334,21 @@ if (process.env.SYNTH_SEED_DEMO !== 'false' && partitions.list().length === 0) {
|
|
|
333
334
|
failureReason: "Health check failed after 3 retries: connection refused on port 8080",
|
|
334
335
|
};
|
|
335
336
|
const dep5: Deployment = {
|
|
336
|
-
id: crypto.randomUUID() as Deployment["id"], artifactId: apiArtifact.id
|
|
337
|
+
id: crypto.randomUUID() as Deployment["id"], input: { type: "deploy" as const, artifactId: apiArtifact.id }, partitionId: acmePartition.id as Deployment["partitionId"],
|
|
337
338
|
environmentId: prodEnv.id as Deployment["environmentId"], version: "1.12.0", status: "succeeded",
|
|
338
339
|
variables: { ...acmePartition.variables, ...prodEnv.variables },
|
|
339
340
|
debriefEntryIds: [],
|
|
340
341
|
createdAt: hoursAgo(12), completedAt: hoursAgo(11.8), failureReason: undefined,
|
|
341
342
|
};
|
|
342
343
|
const dep6: Deployment = {
|
|
343
|
-
id: crypto.randomUUID() as Deployment["id"], artifactId: webAppArtifact.id
|
|
344
|
+
id: crypto.randomUUID() as Deployment["id"], input: { type: "deploy" as const, artifactId: webAppArtifact.id }, partitionId: globexPartition.id as Deployment["partitionId"],
|
|
344
345
|
environmentId: stagingEnv.id as Deployment["environmentId"], version: "2.5.0-rc.1", status: "succeeded",
|
|
345
346
|
variables: { ...globexPartition.variables, ...stagingEnv.variables },
|
|
346
347
|
debriefEntryIds: [],
|
|
347
348
|
createdAt: hoursAgo(6), completedAt: hoursAgo(5.8), failureReason: undefined,
|
|
348
349
|
};
|
|
349
350
|
const dep7: Deployment = {
|
|
350
|
-
id: crypto.randomUUID() as Deployment["id"], artifactId: workerArtifact.id
|
|
351
|
+
id: crypto.randomUUID() as Deployment["id"], input: { type: "deploy" as const, artifactId: workerArtifact.id }, partitionId: initechPartition.id as Deployment["partitionId"],
|
|
351
352
|
environmentId: prodEnv.id as Deployment["environmentId"], version: "2.9.0", status: "failed",
|
|
352
353
|
variables: { ...initechPartition.variables, ...prodEnv.variables },
|
|
353
354
|
debriefEntryIds: [],
|
|
@@ -355,14 +356,14 @@ if (process.env.SYNTH_SEED_DEMO !== 'false' && partitions.list().length === 0) {
|
|
|
355
356
|
failureReason: "Queue depth exceeded threshold (342 > 100) during verification",
|
|
356
357
|
};
|
|
357
358
|
const dep8: Deployment = {
|
|
358
|
-
id: crypto.randomUUID() as Deployment["id"], artifactId: workerArtifact.id
|
|
359
|
+
id: crypto.randomUUID() as Deployment["id"], input: { type: "deploy" as const, artifactId: workerArtifact.id }, partitionId: initechPartition.id as Deployment["partitionId"],
|
|
359
360
|
environmentId: prodEnv.id as Deployment["environmentId"], version: "3.0.0", status: "succeeded",
|
|
360
361
|
variables: { ...initechPartition.variables, ...prodEnv.variables },
|
|
361
362
|
debriefEntryIds: [],
|
|
362
363
|
createdAt: hoursAgo(3), completedAt: hoursAgo(2.7), failureReason: undefined,
|
|
363
364
|
};
|
|
364
365
|
const dep9: Deployment = {
|
|
365
|
-
id: crypto.randomUUID() as Deployment["id"], artifactId: apiArtifact.id
|
|
366
|
+
id: crypto.randomUUID() as Deployment["id"], input: { type: "deploy" as const, artifactId: apiArtifact.id }, partitionId: globexPartition.id as Deployment["partitionId"],
|
|
366
367
|
environmentId: stagingEnv.id as Deployment["environmentId"], version: "1.13.0-beta.2", status: "running",
|
|
367
368
|
variables: { ...globexPartition.variables, ...stagingEnv.variables },
|
|
368
369
|
plan: {
|
|
@@ -378,7 +379,7 @@ if (process.env.SYNTH_SEED_DEMO !== 'false' && partitions.list().length === 0) {
|
|
|
378
379
|
createdAt: hoursAgo(0.5),
|
|
379
380
|
};
|
|
380
381
|
const dep11: Deployment = {
|
|
381
|
-
id: crypto.randomUUID() as Deployment["id"], artifactId: workerArtifact.id
|
|
382
|
+
id: crypto.randomUUID() as Deployment["id"], input: { type: "deploy" as const, artifactId: workerArtifact.id }, partitionId: globexPartition.id as Deployment["partitionId"],
|
|
382
383
|
environmentId: prodEnv.id as Deployment["environmentId"], version: "3.1.0", status: "awaiting_approval",
|
|
383
384
|
variables: { ...globexPartition.variables, ...prodEnv.variables },
|
|
384
385
|
plan: {
|
|
@@ -396,7 +397,7 @@ if (process.env.SYNTH_SEED_DEMO !== 'false' && partitions.list().length === 0) {
|
|
|
396
397
|
createdAt: hoursAgo(0.1),
|
|
397
398
|
};
|
|
398
399
|
const dep10: Deployment = {
|
|
399
|
-
id: crypto.randomUUID() as Deployment["id"], artifactId: webAppArtifact.id
|
|
400
|
+
id: crypto.randomUUID() as Deployment["id"], input: { type: "deploy" as const, artifactId: webAppArtifact.id }, partitionId: initechPartition.id as Deployment["partitionId"],
|
|
400
401
|
environmentId: prodEnv.id as Deployment["environmentId"], version: "2.4.1", status: "rolled_back",
|
|
401
402
|
variables: { ...initechPartition.variables, ...prodEnv.variables },
|
|
402
403
|
debriefEntryIds: [],
|
|
@@ -428,7 +429,7 @@ if (process.env.SYNTH_SEED_DEMO !== 'false' && partitions.list().length === 0) {
|
|
|
428
429
|
// --- Debrief entries (rich decision diary) ---
|
|
429
430
|
|
|
430
431
|
debrief.record({
|
|
431
|
-
partitionId: null,
|
|
432
|
+
partitionId: null, operationId: null, agent: "server", decisionType: "system",
|
|
432
433
|
decision: "Command initialized with demo data",
|
|
433
434
|
reasoning: "Seeded 3 partitions, 3 environments, 3 artifacts, 10 deployments, and 2 envoy security boundary sets.",
|
|
434
435
|
context: { partitions: 3, environments: 3, deployments: 10, artifacts: 3, securityBoundaries: 2 },
|
|
@@ -436,31 +437,31 @@ if (process.env.SYNTH_SEED_DEMO !== 'false' && partitions.list().length === 0) {
|
|
|
436
437
|
|
|
437
438
|
// dep1 — web-app 2.3.0 succeeded
|
|
438
439
|
debrief.record({
|
|
439
|
-
partitionId: acmePartition.id,
|
|
440
|
+
partitionId: acmePartition.id, operationId: dep1.id, agent: "server", decisionType: "pipeline-plan",
|
|
440
441
|
decision: "Planned deployment pipeline for web-app v2.3.0 to Acme Corp production",
|
|
441
442
|
reasoning: "Standard 3-step pipeline: install deps, run migrations, health check. No variable conflicts.",
|
|
442
443
|
context: { version: "2.3.0", steps: 3 },
|
|
443
444
|
});
|
|
444
445
|
debrief.record({
|
|
445
|
-
partitionId: acmePartition.id,
|
|
446
|
+
partitionId: acmePartition.id, operationId: dep1.id, agent: "server", decisionType: "configuration-resolved",
|
|
446
447
|
decision: "Resolved 4 variables for Acme Corp production (partition + environment merged)",
|
|
447
448
|
reasoning: "Merged partition variables (APP_ENV, DB_HOST, REGION) with environment variables (APP_ENV, LOG_LEVEL). APP_ENV conflict resolved: environment value takes precedence.",
|
|
448
449
|
context: { resolvedCount: 4, conflicts: 1, policy: "environment-wins" },
|
|
449
450
|
});
|
|
450
451
|
debrief.record({
|
|
451
|
-
partitionId: acmePartition.id,
|
|
452
|
+
partitionId: acmePartition.id, operationId: dep1.id, agent: "envoy", decisionType: "deployment-execution",
|
|
452
453
|
decision: "Executed deployment web-app v2.3.0 on Acme Corp production",
|
|
453
454
|
reasoning: "All 3 steps completed. Total execution time: 28.4s.",
|
|
454
455
|
context: { duration: 28400 },
|
|
455
456
|
});
|
|
456
457
|
debrief.record({
|
|
457
|
-
partitionId: acmePartition.id,
|
|
458
|
+
partitionId: acmePartition.id, operationId: dep1.id, agent: "envoy", decisionType: "health-check",
|
|
458
459
|
decision: "Health check passed on first attempt",
|
|
459
460
|
reasoning: "GET /health returned 200 with body {\"status\":\"ok\"} in 45ms.",
|
|
460
461
|
context: { attempts: 1, responseTime: 45 },
|
|
461
462
|
});
|
|
462
463
|
debrief.record({
|
|
463
|
-
partitionId: acmePartition.id,
|
|
464
|
+
partitionId: acmePartition.id, operationId: dep1.id, agent: "server", decisionType: "deployment-completion",
|
|
464
465
|
decision: "Deployment web-app v2.3.0 completed successfully",
|
|
465
466
|
reasoning: "All pipeline steps passed. Health check confirmed. Marked as succeeded.",
|
|
466
467
|
context: { status: "succeeded" },
|
|
@@ -468,31 +469,31 @@ if (process.env.SYNTH_SEED_DEMO !== 'false' && partitions.list().length === 0) {
|
|
|
468
469
|
|
|
469
470
|
// dep4 — api-service 1.11.0 failed
|
|
470
471
|
debrief.record({
|
|
471
|
-
partitionId: acmePartition.id,
|
|
472
|
+
partitionId: acmePartition.id, operationId: dep4.id, agent: "server", decisionType: "pipeline-plan",
|
|
472
473
|
decision: "Planned deployment pipeline for api-service v1.11.0 to Acme Corp production",
|
|
473
474
|
reasoning: "2-step pipeline: pull image, verify endpoint.",
|
|
474
475
|
context: { version: "1.11.0", steps: 2 },
|
|
475
476
|
});
|
|
476
477
|
debrief.record({
|
|
477
|
-
partitionId: acmePartition.id,
|
|
478
|
+
partitionId: acmePartition.id, operationId: dep4.id, agent: "envoy", decisionType: "deployment-execution",
|
|
478
479
|
decision: "Image pull succeeded, starting verification",
|
|
479
480
|
reasoning: "docker pull completed in 12.3s. Image sha256:a4f8e... verified.",
|
|
480
481
|
context: { step: "Pull image", duration: 12300 },
|
|
481
482
|
});
|
|
482
483
|
debrief.record({
|
|
483
|
-
partitionId: acmePartition.id,
|
|
484
|
+
partitionId: acmePartition.id, operationId: dep4.id, agent: "envoy", decisionType: "health-check",
|
|
484
485
|
decision: "Health check failed after 3 retries",
|
|
485
486
|
reasoning: "Connection refused on port 8080. Retry 1: refused (5s). Retry 2: refused (10s). Retry 3: refused (15s). Container logs: \"Error: EADDRINUSE :::8080\".",
|
|
486
487
|
context: { attempts: 3, lastError: "ECONNREFUSED", containerLog: "EADDRINUSE" },
|
|
487
488
|
});
|
|
488
489
|
debrief.record({
|
|
489
|
-
partitionId: acmePartition.id,
|
|
490
|
+
partitionId: acmePartition.id, operationId: dep4.id, agent: "envoy", decisionType: "diagnostic-investigation",
|
|
490
491
|
decision: "Root cause: port 8080 bound by stale process from previous deployment",
|
|
491
492
|
reasoning: "Found zombie process from api-service v1.10.0 holding port 8080. Previous deployment did not cleanly shut down.",
|
|
492
493
|
context: { rootCause: "port-conflict", stalePid: 14823 },
|
|
493
494
|
});
|
|
494
495
|
debrief.record({
|
|
495
|
-
partitionId: acmePartition.id,
|
|
496
|
+
partitionId: acmePartition.id, operationId: dep4.id, agent: "server", decisionType: "deployment-failure",
|
|
496
497
|
decision: "Deployment api-service v1.11.0 failed — health check could not connect",
|
|
497
498
|
reasoning: "Envoy diagnostic identified port conflict from stale process. Recommend adding a pre-deploy cleanup step.",
|
|
498
499
|
context: { status: "failed", recommendation: "Add cleanup step" },
|
|
@@ -500,25 +501,25 @@ if (process.env.SYNTH_SEED_DEMO !== 'false' && partitions.list().length === 0) {
|
|
|
500
501
|
|
|
501
502
|
// dep7 — worker-service 2.9.0 failed
|
|
502
503
|
debrief.record({
|
|
503
|
-
partitionId: initechPartition.id,
|
|
504
|
+
partitionId: initechPartition.id, operationId: dep7.id, agent: "server", decisionType: "pipeline-plan",
|
|
504
505
|
decision: "Planned deployment pipeline for worker-service v2.9.0 to Initech production",
|
|
505
506
|
reasoning: "4-step pipeline with full verification strategy.",
|
|
506
507
|
context: { version: "2.9.0", steps: 4, verificationStrategy: "full" },
|
|
507
508
|
});
|
|
508
509
|
debrief.record({
|
|
509
|
-
partitionId: initechPartition.id,
|
|
510
|
+
partitionId: initechPartition.id, operationId: dep7.id, agent: "envoy", decisionType: "deployment-execution",
|
|
510
511
|
decision: "Workers stopped and binary deployed successfully",
|
|
511
512
|
reasoning: "Pre-deploy steps completed. Workers stopped gracefully (0 in-flight jobs lost). Binary copied.",
|
|
512
513
|
context: { stepsCompleted: 2, jobsLost: 0 },
|
|
513
514
|
});
|
|
514
515
|
debrief.record({
|
|
515
|
-
partitionId: initechPartition.id,
|
|
516
|
+
partitionId: initechPartition.id, operationId: dep7.id, agent: "envoy", decisionType: "deployment-verification",
|
|
516
517
|
decision: "Verification failed: queue depth 342 exceeds threshold of 100",
|
|
517
518
|
reasoning: "Workers restarted but queue depth grew rapidly. v2.9.0 introduced a regression in the message processing loop causing 10x slowdown.",
|
|
518
519
|
context: { queueDepth: 342, threshold: 100, processingRate: "0.3/s vs expected 3/s" },
|
|
519
520
|
});
|
|
520
521
|
debrief.record({
|
|
521
|
-
partitionId: initechPartition.id,
|
|
522
|
+
partitionId: initechPartition.id, operationId: dep7.id, agent: "server", decisionType: "deployment-failure",
|
|
522
523
|
decision: "Deployment worker-service v2.9.0 failed — queue depth exceeded threshold",
|
|
523
524
|
reasoning: "Queue depth check returned 342 (max 100). Processing regression in v2.9.0.",
|
|
524
525
|
context: { status: "failed" },
|
|
@@ -526,25 +527,25 @@ if (process.env.SYNTH_SEED_DEMO !== 'false' && partitions.list().length === 0) {
|
|
|
526
527
|
|
|
527
528
|
// dep10 — web-app 2.4.1 rolled back
|
|
528
529
|
debrief.record({
|
|
529
|
-
partitionId: initechPartition.id,
|
|
530
|
+
partitionId: initechPartition.id, operationId: dep10.id, agent: "server", decisionType: "pipeline-plan",
|
|
530
531
|
decision: "Planned deployment pipeline for web-app v2.4.1 to Initech production",
|
|
531
532
|
reasoning: "Standard 3-step pipeline.",
|
|
532
533
|
context: { version: "2.4.1", steps: 3 },
|
|
533
534
|
});
|
|
534
535
|
debrief.record({
|
|
535
|
-
partitionId: initechPartition.id,
|
|
536
|
+
partitionId: initechPartition.id, operationId: dep10.id, agent: "envoy", decisionType: "deployment-execution",
|
|
536
537
|
decision: "All deployment steps completed, starting post-deploy verification",
|
|
537
538
|
reasoning: "Dependencies installed (14.2s), migrations ran (3.1s), health check passed (0.2s).",
|
|
538
539
|
context: { totalDuration: 17500 },
|
|
539
540
|
});
|
|
540
541
|
debrief.record({
|
|
541
|
-
partitionId: initechPartition.id,
|
|
542
|
+
partitionId: initechPartition.id, operationId: dep10.id, agent: "envoy", decisionType: "deployment-verification",
|
|
542
543
|
decision: "Post-deploy smoke test detected 502 errors on /api/v2/users",
|
|
543
544
|
reasoning: "12 endpoint checks: 10 passed, 2 returned 502 (GET and POST /api/v2/users). The v2 users endpoint depends on a schema migration that was partially applied.",
|
|
544
545
|
context: { passed: 10, failed: 2, failedEndpoints: ["/api/v2/users"] },
|
|
545
546
|
});
|
|
546
547
|
debrief.record({
|
|
547
|
-
partitionId: initechPartition.id,
|
|
548
|
+
partitionId: initechPartition.id, operationId: dep10.id, agent: "server", decisionType: "deployment-failure",
|
|
548
549
|
decision: "Initiated rollback of web-app v2.4.1 on Initech production",
|
|
549
550
|
reasoning: "502 errors on critical user endpoints. Rolling back to previous known-good version.",
|
|
550
551
|
context: { status: "rolled_back", rolledBackFrom: "2.4.1" },
|
|
@@ -552,19 +553,19 @@ if (process.env.SYNTH_SEED_DEMO !== 'false' && partitions.list().length === 0) {
|
|
|
552
553
|
|
|
553
554
|
// dep6 — web-app 2.5.0-rc.1 with variable conflict
|
|
554
555
|
debrief.record({
|
|
555
|
-
partitionId: globexPartition.id,
|
|
556
|
+
partitionId: globexPartition.id, operationId: dep6.id, agent: "server", decisionType: "pipeline-plan",
|
|
556
557
|
decision: "Planned deployment for web-app v2.5.0-rc.1 to Globex staging",
|
|
557
558
|
reasoning: "Standard 3-step pipeline. Release candidate — permissive conflict policy.",
|
|
558
559
|
context: { version: "2.5.0-rc.1", steps: 3 },
|
|
559
560
|
});
|
|
560
561
|
debrief.record({
|
|
561
|
-
partitionId: globexPartition.id,
|
|
562
|
+
partitionId: globexPartition.id, operationId: dep6.id, agent: "server", decisionType: "variable-conflict",
|
|
562
563
|
decision: "Variable conflict: APP_ENV defined in both partition and environment",
|
|
563
564
|
reasoning: "Partition sets APP_ENV=production, environment sets APP_ENV=staging. Permissive policy — using environment value.",
|
|
564
565
|
context: { variable: "APP_ENV", partitionValue: "production", environmentValue: "staging", resolution: "environment-wins" },
|
|
565
566
|
});
|
|
566
567
|
debrief.record({
|
|
567
|
-
partitionId: globexPartition.id,
|
|
568
|
+
partitionId: globexPartition.id, operationId: dep6.id, agent: "server", decisionType: "deployment-completion",
|
|
568
569
|
decision: "Deployment web-app v2.5.0-rc.1 completed on Globex staging",
|
|
569
570
|
reasoning: "All steps passed despite variable conflict. RC verified in staging.",
|
|
570
571
|
context: { status: "succeeded" },
|
|
@@ -572,13 +573,13 @@ if (process.env.SYNTH_SEED_DEMO !== 'false' && partitions.list().length === 0) {
|
|
|
572
573
|
|
|
573
574
|
// dep9 — in-progress
|
|
574
575
|
debrief.record({
|
|
575
|
-
partitionId: globexPartition.id,
|
|
576
|
+
partitionId: globexPartition.id, operationId: dep9.id, agent: "server", decisionType: "pipeline-plan",
|
|
576
577
|
decision: "Planned deployment for api-service v1.13.0-beta.2 to Globex staging",
|
|
577
578
|
reasoning: "2-step pipeline for staging. Beta version — monitoring closely.",
|
|
578
579
|
context: { version: "1.13.0-beta.2", steps: 2 },
|
|
579
580
|
});
|
|
580
581
|
debrief.record({
|
|
581
|
-
partitionId: globexPartition.id,
|
|
582
|
+
partitionId: globexPartition.id, operationId: dep9.id, agent: "envoy", decisionType: "deployment-execution",
|
|
582
583
|
decision: "Image pull in progress for api-service v1.13.0-beta.2",
|
|
583
584
|
reasoning: "Pulling docker image from registry. Download progress: 67%.",
|
|
584
585
|
context: { step: "Pull image", progress: "67%" },
|
|
@@ -586,13 +587,13 @@ if (process.env.SYNTH_SEED_DEMO !== 'false' && partitions.list().length === 0) {
|
|
|
586
587
|
|
|
587
588
|
// Environment scans
|
|
588
589
|
debrief.record({
|
|
589
|
-
partitionId: acmePartition.id,
|
|
590
|
+
partitionId: acmePartition.id, operationId: null, agent: "envoy", decisionType: "environment-scan",
|
|
590
591
|
decision: "Environment scan completed for Acme Corp production",
|
|
591
592
|
reasoning: "Current versions: web-app v2.4.1, api-service v1.12.0. Disk: 62%. Memory: 71%. No drift detected.",
|
|
592
593
|
context: { versions: { "web-app": "2.4.1", "api-service": "1.12.0" }, diskUsage: "62%", memoryUsage: "71%" },
|
|
593
594
|
});
|
|
594
595
|
debrief.record({
|
|
595
|
-
partitionId: initechPartition.id,
|
|
596
|
+
partitionId: initechPartition.id, operationId: null, agent: "envoy", decisionType: "environment-scan",
|
|
596
597
|
decision: "Environment scan for Initech production — drift detected",
|
|
597
598
|
reasoning: "worker-service v3.0.0 running. web-app at v2.4.0 (v2.4.1 was rolled back). Drift: LOG_LEVEL manually changed from 'warn' to 'debug' outside deployment pipeline.",
|
|
598
599
|
context: { drift: true, driftDetails: "LOG_LEVEL changed outside pipeline" },
|
|
@@ -706,7 +707,7 @@ registerHealthRoutes(app, {
|
|
|
706
707
|
});
|
|
707
708
|
const progressStore = new ProgressEventStore();
|
|
708
709
|
const defaultEnvoyClient = new EnvoyClient(settings.get().envoy.url, settings.get().envoy.timeoutMs);
|
|
709
|
-
|
|
710
|
+
registerOperationRoutes(app, deployments, debrief, partitions, environments, artifactStore, settings, telemetryStore, progressStore, defaultEnvoyClient, envoyRegistry, llm);
|
|
710
711
|
registerEnvoyReportRoutes(app, debrief, deployments, envoyRegistry);
|
|
711
712
|
registerArtifactRoutes(app, artifactStore, telemetryStore, artifactAnalyzer);
|
|
712
713
|
registerSecurityBoundaryRoutes(app, securityBoundaryStore, telemetryStore);
|
|
@@ -757,6 +758,11 @@ for (const ch of intakeChannelStore.list()) {
|
|
|
757
758
|
}
|
|
758
759
|
}
|
|
759
760
|
|
|
761
|
+
// --- Alert Webhooks (external monitoring triggers) ---
|
|
762
|
+
|
|
763
|
+
const alertWebhookStore = new PersistentAlertWebhookStore(entityDb);
|
|
764
|
+
registerAlertWebhookRoutes(app, alertWebhookStore, deployments, debrief, environments, partitions, telemetryStore, envoyRegistry);
|
|
765
|
+
|
|
760
766
|
// --- Serve UI static files if built ---
|
|
761
767
|
|
|
762
768
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|