@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/dist/middleware/auth.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SignJWT, jwtVerify } from "jose";
|
|
2
2
|
const EXEMPT_ROUTES = ["/health", "/api/health", "/api/auth/login", "/api/auth/register", "/api/auth/refresh", "/api/auth/status", "/api/auth/providers", "/api/envoy/report"];
|
|
3
|
-
const EXEMPT_PREFIXES = ["/api/auth/oidc/", "/api/auth/callback/oidc/", "/api/auth/saml/", "/api/auth/callback/saml/", "/api/auth/ldap/", "/api/intake/webhook/"];
|
|
3
|
+
const EXEMPT_PREFIXES = ["/api/auth/oidc/", "/api/auth/callback/oidc/", "/api/auth/saml/", "/api/auth/callback/saml/", "/api/auth/ldap/", "/api/intake/webhook/", "/api/alert-webhooks/receive/"];
|
|
4
4
|
// Envoy callback endpoints — validated by envoy token, not user JWT
|
|
5
5
|
const EXEMPT_PATTERNS = [/^\/api\/deployments\/[^/]+\/progress$/];
|
|
6
6
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAgB1C,MAAM,aAAa,GAAG,CAAC,SAAS,EAAE,aAAa,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,mBAAmB,CAAC,CAAC;AAC/K,MAAM,eAAe,GAAG,CAAC,iBAAiB,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,sBAAsB,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/middleware/auth.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
HealthCheckResult,
|
|
4
4
|
} from "./health-checker.js";
|
|
5
5
|
import { serverLog, serverWarn, serverError } from "../logger.js";
|
|
6
|
-
import type { DecisionType, DeploymentPlan, PlannedStep, SecurityBoundary } from "@synth-deploy/core";
|
|
6
|
+
import type { DecisionType, DeploymentPlan, PlannedStep, SecurityBoundary, MonitoringDirective } from "@synth-deploy/core";
|
|
7
7
|
|
|
8
8
|
// ---------------------------------------------------------------------------
|
|
9
9
|
// Types — Envoy API responses
|
|
@@ -11,6 +11,7 @@ import type { DecisionType, DeploymentPlan, PlannedStep, SecurityBoundary } from
|
|
|
11
11
|
|
|
12
12
|
export interface EnvoyDeployResult {
|
|
13
13
|
deploymentId: string;
|
|
14
|
+
operationId: string;
|
|
14
15
|
success: boolean;
|
|
15
16
|
workspacePath: string;
|
|
16
17
|
artifacts: string[];
|
|
@@ -25,7 +26,7 @@ export interface EnvoyDeployResult {
|
|
|
25
26
|
id: string;
|
|
26
27
|
timestamp: string;
|
|
27
28
|
partitionId: string | null;
|
|
28
|
-
|
|
29
|
+
operationId: string | null;
|
|
29
30
|
agent: "server" | "envoy";
|
|
30
31
|
decisionType: DecisionType;
|
|
31
32
|
decision: string;
|
|
@@ -176,9 +177,9 @@ export class EnvoyClient {
|
|
|
176
177
|
*/
|
|
177
178
|
async deploy(instruction: {
|
|
178
179
|
deploymentId: string;
|
|
180
|
+
operationId: string;
|
|
179
181
|
partitionId: string;
|
|
180
182
|
environmentId: string;
|
|
181
|
-
operationId: string;
|
|
182
183
|
version: string;
|
|
183
184
|
variables: Record<string, string>;
|
|
184
185
|
environmentName: string;
|
|
@@ -202,7 +203,7 @@ export class EnvoyClient {
|
|
|
202
203
|
* Dispatch an approved plan to the Envoy for deterministic execution.
|
|
203
204
|
*/
|
|
204
205
|
async executeApprovedPlan(params: {
|
|
205
|
-
|
|
206
|
+
operationId: string;
|
|
206
207
|
plan: DeploymentPlan;
|
|
207
208
|
rollbackPlan: DeploymentPlan;
|
|
208
209
|
artifactType: string;
|
|
@@ -211,7 +212,7 @@ export class EnvoyClient {
|
|
|
211
212
|
progressCallbackUrl?: string;
|
|
212
213
|
callbackToken?: string;
|
|
213
214
|
}): Promise<EnvoyDeployResult> {
|
|
214
|
-
serverLog("ENVOY-EXECUTE", {
|
|
215
|
+
serverLog("ENVOY-EXECUTE", { operationId: params.operationId, envoyUrl: this.baseUrl, artifact: params.artifactName, steps: params.plan?.steps?.length ?? 0 });
|
|
215
216
|
const execStart = Date.now();
|
|
216
217
|
const response = await fetchWithRetry(
|
|
217
218
|
`${this.baseUrl}/execute`,
|
|
@@ -225,9 +226,9 @@ export class EnvoyClient {
|
|
|
225
226
|
|
|
226
227
|
const execResult = (await response.json()) as EnvoyDeployResult;
|
|
227
228
|
if (execResult.success) {
|
|
228
|
-
serverLog("ENVOY-EXECUTE-COMPLETE", {
|
|
229
|
+
serverLog("ENVOY-EXECUTE-COMPLETE", { operationId: params.operationId, durationMs: Date.now() - execStart });
|
|
229
230
|
} else {
|
|
230
|
-
serverLog("ENVOY-EXECUTE-FAILED", {
|
|
231
|
+
serverLog("ENVOY-EXECUTE-FAILED", { operationId: params.operationId, failureReason: execResult.failureReason, durationMs: Date.now() - execStart });
|
|
231
232
|
}
|
|
232
233
|
return execResult;
|
|
233
234
|
}
|
|
@@ -238,8 +239,13 @@ export class EnvoyClient {
|
|
|
238
239
|
* POST /api/deployments/:id/plan to move the deployment to awaiting_approval.
|
|
239
240
|
*/
|
|
240
241
|
async requestPlan(params: {
|
|
241
|
-
|
|
242
|
-
|
|
242
|
+
operationId: string;
|
|
243
|
+
operationType?: "deploy" | "query" | "investigate" | "maintain" | "trigger";
|
|
244
|
+
intent?: string;
|
|
245
|
+
allowWrite?: boolean;
|
|
246
|
+
triggerCondition?: string;
|
|
247
|
+
triggerResponseIntent?: string;
|
|
248
|
+
artifact?: {
|
|
243
249
|
id: string;
|
|
244
250
|
name: string;
|
|
245
251
|
type: string;
|
|
@@ -264,10 +270,21 @@ export class EnvoyClient {
|
|
|
264
270
|
version: string;
|
|
265
271
|
resolvedVariables: Record<string, string>;
|
|
266
272
|
refinementFeedback?: string;
|
|
267
|
-
}): Promise<{
|
|
273
|
+
}): Promise<{
|
|
274
|
+
plan: DeploymentPlan;
|
|
275
|
+
rollbackPlan: DeploymentPlan;
|
|
276
|
+
delta?: string;
|
|
277
|
+
assessmentSummary?: string;
|
|
278
|
+
blocked?: boolean;
|
|
279
|
+
blockReason?: string;
|
|
280
|
+
queryFindings?: { targetsSurveyed: string[]; summary: string; findings: Array<{ target: string; observations: string[] }> };
|
|
281
|
+
investigationFindings?: { targetsSurveyed: string[]; summary: string; findings: Array<{ target: string; observations: string[] }>; rootCause?: string; proposedResolution?: { intent: string; operationType: "deploy" | "maintain"; parameters?: Record<string, unknown> } };
|
|
282
|
+
intervalMs?: number;
|
|
283
|
+
cooldownMs?: number;
|
|
284
|
+
}> {
|
|
268
285
|
// Forward the LLM API key so the Envoy can use it if it started without one.
|
|
269
286
|
// Sent in the request body (not headers) over the trusted server↔envoy channel.
|
|
270
|
-
serverLog("ENVOY-PLAN-REQUEST", {
|
|
287
|
+
serverLog("ENVOY-PLAN-REQUEST", { operationId: params.operationId, envoyUrl: this.baseUrl, artifact: params.artifact?.name, version: params.version, environment: params.environment.name });
|
|
271
288
|
const llmApiKey = process.env.SYNTH_LLM_API_KEY;
|
|
272
289
|
const planStart = Date.now();
|
|
273
290
|
const response = await fetchWithRetry(
|
|
@@ -282,12 +299,12 @@ export class EnvoyClient {
|
|
|
282
299
|
|
|
283
300
|
if (!response.ok) {
|
|
284
301
|
const body = await response.text().catch(() => "");
|
|
285
|
-
serverLog("ENVOY-PLAN-FAILED", {
|
|
302
|
+
serverLog("ENVOY-PLAN-FAILED", { operationId: params.operationId, status: response.status, durationMs: Date.now() - planStart });
|
|
286
303
|
throw new Error(`Envoy planning failed (HTTP ${response.status}): ${body}`);
|
|
287
304
|
}
|
|
288
305
|
|
|
289
|
-
const planResult = (await response.json()) as
|
|
290
|
-
serverLog("ENVOY-PLAN-RECEIVED", {
|
|
306
|
+
const planResult = (await response.json()) as Awaited<ReturnType<typeof this.requestPlan>>;
|
|
307
|
+
serverLog("ENVOY-PLAN-RECEIVED", { operationId: params.operationId, steps: planResult.plan?.steps?.length ?? 0, blocked: planResult.blocked ?? false, durationMs: Date.now() - planStart });
|
|
291
308
|
return planResult;
|
|
292
309
|
}
|
|
293
310
|
|
|
@@ -297,7 +314,7 @@ export class EnvoyClient {
|
|
|
297
314
|
* than the forward plan, so the rollback is targeted to what actually changed.
|
|
298
315
|
*/
|
|
299
316
|
async requestRollbackPlan(params: {
|
|
300
|
-
|
|
317
|
+
operationId: string;
|
|
301
318
|
artifact: {
|
|
302
319
|
name: string;
|
|
303
320
|
type: string;
|
|
@@ -381,6 +398,81 @@ export class EnvoyClient {
|
|
|
381
398
|
);
|
|
382
399
|
return (await response.json()) as { mode: "replan" | "rejection" | "response"; message: string };
|
|
383
400
|
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Install a monitoring directive on the Envoy for health-based triggering.
|
|
404
|
+
*/
|
|
405
|
+
async installMonitoringDirective(directive: MonitoringDirective): Promise<void> {
|
|
406
|
+
serverLog("ENVOY-MONITOR-INSTALL", { directiveId: directive.id, envoyUrl: this.baseUrl });
|
|
407
|
+
const response = await fetchWithRetry(
|
|
408
|
+
`${this.baseUrl}/monitor`,
|
|
409
|
+
{
|
|
410
|
+
method: "POST",
|
|
411
|
+
headers: { "Content-Type": "application/json" },
|
|
412
|
+
body: JSON.stringify(directive),
|
|
413
|
+
},
|
|
414
|
+
this.timeoutMs,
|
|
415
|
+
);
|
|
416
|
+
if (!response.ok) {
|
|
417
|
+
const body = await response.text().catch(() => "");
|
|
418
|
+
throw new Error(`Failed to install monitoring directive (HTTP ${response.status}): ${body}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Pause a monitoring directive on the Envoy.
|
|
424
|
+
*/
|
|
425
|
+
async pauseMonitoringDirective(directiveId: string): Promise<void> {
|
|
426
|
+
const response = await fetchWithRetry(
|
|
427
|
+
`${this.baseUrl}/monitor/${directiveId}/pause`,
|
|
428
|
+
{ method: "POST", headers: { "Content-Type": "application/json" } },
|
|
429
|
+
this.timeoutMs,
|
|
430
|
+
);
|
|
431
|
+
if (!response.ok) {
|
|
432
|
+
throw new Error(`Failed to pause monitoring directive (HTTP ${response.status})`);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Resume a monitoring directive on the Envoy.
|
|
438
|
+
*/
|
|
439
|
+
async resumeMonitoringDirective(directiveId: string): Promise<void> {
|
|
440
|
+
const response = await fetchWithRetry(
|
|
441
|
+
`${this.baseUrl}/monitor/${directiveId}/resume`,
|
|
442
|
+
{ method: "POST", headers: { "Content-Type": "application/json" } },
|
|
443
|
+
this.timeoutMs,
|
|
444
|
+
);
|
|
445
|
+
if (!response.ok) {
|
|
446
|
+
throw new Error(`Failed to resume monitoring directive (HTTP ${response.status})`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Remove a monitoring directive from the Envoy.
|
|
452
|
+
*/
|
|
453
|
+
async removeMonitoringDirective(directiveId: string): Promise<void> {
|
|
454
|
+
const response = await fetchWithRetry(
|
|
455
|
+
`${this.baseUrl}/monitor/${directiveId}`,
|
|
456
|
+
{ method: "DELETE", headers: { "Content-Type": "application/json" } },
|
|
457
|
+
this.timeoutMs,
|
|
458
|
+
);
|
|
459
|
+
if (!response.ok) {
|
|
460
|
+
throw new Error(`Failed to remove monitoring directive (HTTP ${response.status})`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* List active monitoring directives on the Envoy.
|
|
466
|
+
*/
|
|
467
|
+
async listMonitoringDirectives(): Promise<Array<{ directive: MonitoringDirective; lastFiredAt: number; fireCount: number; suppressedCount: number }>> {
|
|
468
|
+
const response = await fetchWithRetry(
|
|
469
|
+
`${this.baseUrl}/monitor`,
|
|
470
|
+
{},
|
|
471
|
+
this.timeoutMs,
|
|
472
|
+
);
|
|
473
|
+
const data = (await response.json()) as { directives: Array<{ directive: MonitoringDirective; lastFiredAt: number; fireCount: number; suppressedCount: number }> };
|
|
474
|
+
return data.directives;
|
|
475
|
+
}
|
|
384
476
|
}
|
|
385
477
|
|
|
386
478
|
// ---------------------------------------------------------------------------
|
|
@@ -37,7 +37,7 @@ export function markStaleDeployments(
|
|
|
37
37
|
|
|
38
38
|
debrief.record({
|
|
39
39
|
partitionId: deployment.partitionId ?? null,
|
|
40
|
-
|
|
40
|
+
operationId: deployment.id,
|
|
41
41
|
agent: "server",
|
|
42
42
|
decisionType: "deployment-failure",
|
|
43
43
|
decision: `Marked deployment as failed: exceeded ${Math.round(thresholdMs / 60_000)} minute stale threshold`,
|
package/src/agent/synth-agent.ts
CHANGED
|
@@ -2,7 +2,8 @@ import crypto from "node:crypto";
|
|
|
2
2
|
import type {
|
|
3
3
|
Deployment,
|
|
4
4
|
DeploymentId,
|
|
5
|
-
|
|
5
|
+
OperationInput,
|
|
6
|
+
OperationTrigger,
|
|
6
7
|
DebriefWriter,
|
|
7
8
|
Environment,
|
|
8
9
|
Partition,
|
|
@@ -25,7 +26,7 @@ import { serverLog, serverError } from "../logger.js";
|
|
|
25
26
|
// Public interfaces
|
|
26
27
|
// ---------------------------------------------------------------------------
|
|
27
28
|
|
|
28
|
-
export interface
|
|
29
|
+
export interface OperationStore {
|
|
29
30
|
save(deployment: Deployment): void;
|
|
30
31
|
get(id: DeploymentId): Deployment | undefined;
|
|
31
32
|
getByPartition(partitionId: string): Deployment[];
|
|
@@ -36,6 +37,8 @@ export interface DeploymentStore {
|
|
|
36
37
|
findRecentByArtifact(artifactId: string, since: Date, status?: string): Deployment[];
|
|
37
38
|
findLatestByEnvironment(envId: string): Deployment | undefined;
|
|
38
39
|
}
|
|
40
|
+
/** @deprecated Use OperationStore */
|
|
41
|
+
export type DeploymentStore = OperationStore;
|
|
39
42
|
|
|
40
43
|
export interface AgentOptions {
|
|
41
44
|
/** Number of health check retries after initial failure. Default: 1 */
|
|
@@ -172,7 +175,7 @@ export class SynthAgent {
|
|
|
172
175
|
|
|
173
176
|
constructor(
|
|
174
177
|
private debrief: DebriefWriter,
|
|
175
|
-
private deployments:
|
|
178
|
+
private deployments: OperationStore,
|
|
176
179
|
private artifactStore: IArtifactStore,
|
|
177
180
|
private environmentStore: IEnvironmentStore,
|
|
178
181
|
private partitionStore: IPartitionStore,
|
|
@@ -217,14 +220,23 @@ export class SynthAgent {
|
|
|
217
220
|
// Main entry point
|
|
218
221
|
// -----------------------------------------------------------------------
|
|
219
222
|
|
|
220
|
-
async
|
|
221
|
-
|
|
223
|
+
async triggerOperation(
|
|
224
|
+
input: OperationInput,
|
|
225
|
+
trigger: OperationTrigger,
|
|
222
226
|
): Promise<Deployment> {
|
|
227
|
+
if (input.type !== "deploy") {
|
|
228
|
+
throw new OrchestrationError(
|
|
229
|
+
"resolve-entities",
|
|
230
|
+
`Operation type "${input.type}" is not supported via programmatic triggers`,
|
|
231
|
+
`"query" and "investigate" operations run through the REST API path (web UI), not programmatic triggers. Only "deploy" is supported via triggerOperation().`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
223
235
|
const deploymentId = crypto.randomUUID();
|
|
224
236
|
|
|
225
237
|
// --- Look up entities from stores -----------------------------------------------
|
|
226
238
|
|
|
227
|
-
serverLog("DEPLOY-TRIGGER", { deploymentId, artifactId:
|
|
239
|
+
serverLog("DEPLOY-TRIGGER", { operationId: deploymentId, artifactId: input.artifactId, environmentId: trigger.environmentId, partitionId: trigger.partitionId ?? null, triggeredBy: trigger.triggeredBy });
|
|
228
240
|
|
|
229
241
|
const environment = this.environmentStore.get(trigger.environmentId);
|
|
230
242
|
if (!environment) {
|
|
@@ -249,12 +261,12 @@ export class SynthAgent {
|
|
|
249
261
|
}
|
|
250
262
|
}
|
|
251
263
|
|
|
252
|
-
const artifact = this.artifactStore.get(
|
|
264
|
+
const artifact = this.artifactStore.get(input.artifactId);
|
|
253
265
|
if (!artifact) {
|
|
254
266
|
throw new OrchestrationError(
|
|
255
267
|
"resolve-entities",
|
|
256
|
-
`Artifact not found: ${
|
|
257
|
-
`The trigger references artifact ID "${
|
|
268
|
+
`Artifact not found: ${input.artifactId}`,
|
|
269
|
+
`The trigger references artifact ID "${input.artifactId}" which does not exist in the artifact store. ` +
|
|
258
270
|
`Verify the artifact ID is correct and the artifact has been created.`,
|
|
259
271
|
);
|
|
260
272
|
}
|
|
@@ -275,7 +287,7 @@ export class SynthAgent {
|
|
|
275
287
|
|
|
276
288
|
const analysisEntry = this.debrief.record({
|
|
277
289
|
partitionId: trigger.partitionId ?? null,
|
|
278
|
-
deploymentId,
|
|
290
|
+
operationId: deploymentId,
|
|
279
291
|
agent: "server",
|
|
280
292
|
decisionType: "artifact-analysis",
|
|
281
293
|
decision: `Analyzed artifact "${artifact.name}" (${artifact.type}) — confidence ${artifact.analysis.confidence}`,
|
|
@@ -299,7 +311,7 @@ export class SynthAgent {
|
|
|
299
311
|
|
|
300
312
|
// --- Step 1: Plan the pipeline -----------------------------------------
|
|
301
313
|
|
|
302
|
-
const version =
|
|
314
|
+
const version = input.artifactVersionId ?? "latest";
|
|
303
315
|
const pipelineSteps = [
|
|
304
316
|
"resolve-configuration",
|
|
305
317
|
"preflight-health-check",
|
|
@@ -309,7 +321,7 @@ export class SynthAgent {
|
|
|
309
321
|
|
|
310
322
|
const planEntry = this.debrief.record({
|
|
311
323
|
partitionId: trigger.partitionId ?? null,
|
|
312
|
-
deploymentId,
|
|
324
|
+
operationId: deploymentId,
|
|
313
325
|
agent: "server",
|
|
314
326
|
decisionType: "pipeline-plan",
|
|
315
327
|
decision: `Planned deployment pipeline: ${pipelineSteps.join(" → ")}`,
|
|
@@ -349,7 +361,7 @@ export class SynthAgent {
|
|
|
349
361
|
|
|
350
362
|
const planGenEntry = this.debrief.record({
|
|
351
363
|
partitionId: trigger.partitionId ?? null,
|
|
352
|
-
deploymentId,
|
|
364
|
+
operationId: deploymentId,
|
|
353
365
|
agent: "server",
|
|
354
366
|
decisionType: "plan-generation",
|
|
355
367
|
decision: `Generated deployment plan for "${artifact.name}" v${version} — ${initialPlan.steps.length} step(s)`,
|
|
@@ -363,7 +375,7 @@ export class SynthAgent {
|
|
|
363
375
|
|
|
364
376
|
const approvalEntry = this.debrief.record({
|
|
365
377
|
partitionId: trigger.partitionId ?? null,
|
|
366
|
-
deploymentId,
|
|
378
|
+
operationId: deploymentId,
|
|
367
379
|
agent: "server",
|
|
368
380
|
decisionType: "plan-approval",
|
|
369
381
|
decision: `Auto-approved deployment plan for "${artifact.name}" v${version}`,
|
|
@@ -378,8 +390,7 @@ export class SynthAgent {
|
|
|
378
390
|
|
|
379
391
|
const deployment: Deployment = {
|
|
380
392
|
id: deploymentId,
|
|
381
|
-
|
|
382
|
-
artifactVersionId: trigger.artifactVersionId,
|
|
393
|
+
input,
|
|
383
394
|
environmentId: trigger.environmentId,
|
|
384
395
|
partitionId: trigger.partitionId,
|
|
385
396
|
version,
|
|
@@ -397,7 +408,7 @@ export class SynthAgent {
|
|
|
397
408
|
try {
|
|
398
409
|
// --- Step 2: Resolve configuration -----------------------------------
|
|
399
410
|
|
|
400
|
-
serverLog("DEPLOY-CONFIG-RESOLVE", {
|
|
411
|
+
serverLog("DEPLOY-CONFIG-RESOLVE", { operationId: deployment.id });
|
|
401
412
|
const { variables, hasConflicts } = this.resolveConfiguration(
|
|
402
413
|
deployment,
|
|
403
414
|
trigger.variables,
|
|
@@ -411,13 +422,13 @@ export class SynthAgent {
|
|
|
411
422
|
|
|
412
423
|
deployment.status = "running";
|
|
413
424
|
this.deployments.save(deployment);
|
|
414
|
-
serverLog("DEPLOY-HEALTH-CHECK", {
|
|
425
|
+
serverLog("DEPLOY-HEALTH-CHECK", { operationId: deployment.id, environment: environment.name });
|
|
415
426
|
|
|
416
427
|
await this.preflightHealthCheck(deployment, partition, environment, artifact);
|
|
417
428
|
|
|
418
429
|
// --- Step 4: Execute deployment ----------------------------------------
|
|
419
430
|
|
|
420
|
-
serverLog("DEPLOY-EXECUTE", {
|
|
431
|
+
serverLog("DEPLOY-EXECUTE", { operationId: deployment.id, artifact: artifact.name, version: deployment.version, environment: environment.name });
|
|
421
432
|
const delegated = await this.executeDeployment(deployment, partition, environment, artifact);
|
|
422
433
|
|
|
423
434
|
// --- Step 5: Post-deploy verify ----------------------------------------
|
|
@@ -433,11 +444,11 @@ export class SynthAgent {
|
|
|
433
444
|
|
|
434
445
|
deployment.status = "succeeded";
|
|
435
446
|
deployment.completedAt = new Date();
|
|
436
|
-
serverLog("DEPLOY-SUCCEEDED", {
|
|
447
|
+
serverLog("DEPLOY-SUCCEEDED", { operationId: deployment.id, artifact: artifact.name, version: deployment.version, environment: environment.name, durationMs: deployment.completedAt.getTime() - deployment.createdAt.getTime() });
|
|
437
448
|
|
|
438
449
|
const completionEntry = this.debrief.record({
|
|
439
450
|
partitionId: deployment.partitionId ?? null,
|
|
440
|
-
|
|
451
|
+
operationId: deployment.id,
|
|
441
452
|
agent: "server",
|
|
442
453
|
decisionType: "deployment-completion",
|
|
443
454
|
decision: `Marking deployment of ${artifact.name} v${deployment.version} as succeeded on "${environment.name}"`,
|
|
@@ -465,11 +476,11 @@ export class SynthAgent {
|
|
|
465
476
|
error instanceof OrchestrationError
|
|
466
477
|
? error.message
|
|
467
478
|
: `Unexpected error: ${error instanceof Error ? error.message : String(error)}`;
|
|
468
|
-
serverError("DEPLOY-FAILED", {
|
|
479
|
+
serverError("DEPLOY-FAILED", { operationId: deployment.id, reason: deployment.failureReason, durationMs: deployment.completedAt.getTime() - deployment.createdAt.getTime() });
|
|
469
480
|
|
|
470
481
|
const failEntry = this.debrief.record({
|
|
471
482
|
partitionId: deployment.partitionId ?? null,
|
|
472
|
-
|
|
483
|
+
operationId: deployment.id,
|
|
473
484
|
agent: "server",
|
|
474
485
|
decisionType: "deployment-failure",
|
|
475
486
|
decision: `Deployment failed: ${deployment.failureReason}`,
|
|
@@ -532,7 +543,7 @@ export class SynthAgent {
|
|
|
532
543
|
|
|
533
544
|
this.debrief.record({
|
|
534
545
|
partitionId,
|
|
535
|
-
|
|
546
|
+
operationId: null,
|
|
536
547
|
agent: "server",
|
|
537
548
|
decisionType: "diagnostic-investigation",
|
|
538
549
|
decision: `${tools.length} external tool(s) available from ${connectedServers.length} MCP server(s)`,
|
|
@@ -557,7 +568,7 @@ export class SynthAgent {
|
|
|
557
568
|
// Never let external checks block the deployment
|
|
558
569
|
this.debrief.record({
|
|
559
570
|
partitionId,
|
|
560
|
-
|
|
571
|
+
operationId: null,
|
|
561
572
|
agent: "server",
|
|
562
573
|
decisionType: "diagnostic-investigation",
|
|
563
574
|
decision: "External MCP check failed — proceeding without external intelligence",
|
|
@@ -648,7 +659,7 @@ export class SynthAgent {
|
|
|
648
659
|
const partitionVarCount = partition ? Object.keys(partition.variables).length : 0;
|
|
649
660
|
const configEntry = this.debrief.record({
|
|
650
661
|
partitionId: deployment.partitionId ?? null,
|
|
651
|
-
|
|
662
|
+
operationId: deployment.id,
|
|
652
663
|
agent: "server",
|
|
653
664
|
decisionType: "configuration-resolved",
|
|
654
665
|
decision:
|
|
@@ -828,7 +839,7 @@ export class SynthAgent {
|
|
|
828
839
|
if (crossEnvConn) {
|
|
829
840
|
const entry = this.debrief.record({
|
|
830
841
|
partitionId: deployment.partitionId ?? null,
|
|
831
|
-
|
|
842
|
+
operationId: deployment.id,
|
|
832
843
|
agent: "server",
|
|
833
844
|
decisionType: "variable-conflict",
|
|
834
845
|
decision:
|
|
@@ -859,7 +870,7 @@ export class SynthAgent {
|
|
|
859
870
|
.join("; ");
|
|
860
871
|
const entry = this.debrief.record({
|
|
861
872
|
partitionId: deployment.partitionId ?? null,
|
|
862
|
-
|
|
873
|
+
operationId: deployment.id,
|
|
863
874
|
agent: "server",
|
|
864
875
|
decisionType: "variable-conflict",
|
|
865
876
|
decision: `Cross-environment variable pattern in ${crossEnv.length} non-connectivity variable(s)`,
|
|
@@ -884,7 +895,7 @@ export class SynthAgent {
|
|
|
884
895
|
if (sensitiveDetails) {
|
|
885
896
|
const entry = this.debrief.record({
|
|
886
897
|
partitionId: deployment.partitionId ?? null,
|
|
887
|
-
|
|
898
|
+
operationId: deployment.id,
|
|
888
899
|
agent: "server",
|
|
889
900
|
decisionType: "variable-conflict",
|
|
890
901
|
decision: `Security-sensitive variable(s) overridden: ${sensitiveDetails.map((d) => d.conflict.variable).join(", ")}`,
|
|
@@ -919,7 +930,7 @@ export class SynthAgent {
|
|
|
919
930
|
.join("; ");
|
|
920
931
|
const entry = this.debrief.record({
|
|
921
932
|
partitionId: deployment.partitionId ?? null,
|
|
922
|
-
|
|
933
|
+
operationId: deployment.id,
|
|
923
934
|
agent: "server",
|
|
924
935
|
decisionType: "variable-conflict",
|
|
925
936
|
decision: `Resolved ${standardDetails.length} variable conflict(s) via precedence rules`,
|
|
@@ -993,7 +1004,7 @@ export class SynthAgent {
|
|
|
993
1004
|
if (firstCheck.reachable) {
|
|
994
1005
|
const entry = this.debrief.record({
|
|
995
1006
|
partitionId: deployment.partitionId ?? null,
|
|
996
|
-
|
|
1007
|
+
operationId: deployment.id,
|
|
997
1008
|
agent: "server",
|
|
998
1009
|
decisionType: "health-check",
|
|
999
1010
|
decision: `Proceeding with deployment — target environment "${environment.name}" confirmed healthy in ${firstCheck.responseTimeMs}ms`,
|
|
@@ -1025,7 +1036,7 @@ export class SynthAgent {
|
|
|
1025
1036
|
// Reasoning determined retrying won't help (e.g., DNS failure)
|
|
1026
1037
|
const abortEntry = this.debrief.record({
|
|
1027
1038
|
partitionId: deployment.partitionId ?? null,
|
|
1028
|
-
|
|
1039
|
+
operationId: deployment.id,
|
|
1029
1040
|
agent: "server",
|
|
1030
1041
|
decisionType: "health-check",
|
|
1031
1042
|
decision: "Pre-flight health check failed — aborting without retry",
|
|
@@ -1050,7 +1061,7 @@ export class SynthAgent {
|
|
|
1050
1061
|
// Decision is to retry
|
|
1051
1062
|
const retryEntry = this.debrief.record({
|
|
1052
1063
|
partitionId: deployment.partitionId ?? null,
|
|
1053
|
-
|
|
1064
|
+
operationId: deployment.id,
|
|
1054
1065
|
agent: "server",
|
|
1055
1066
|
decisionType: "health-check",
|
|
1056
1067
|
decision: "Pre-flight health check failed — attempting retry",
|
|
@@ -1079,7 +1090,7 @@ export class SynthAgent {
|
|
|
1079
1090
|
if (retryCheck.reachable) {
|
|
1080
1091
|
const recoveryEntry = this.debrief.record({
|
|
1081
1092
|
partitionId: deployment.partitionId ?? null,
|
|
1082
|
-
|
|
1093
|
+
operationId: deployment.id,
|
|
1083
1094
|
agent: "server",
|
|
1084
1095
|
decisionType: "health-check",
|
|
1085
1096
|
decision:
|
|
@@ -1297,7 +1308,7 @@ export class SynthAgent {
|
|
|
1297
1308
|
// No settingsReader configured — execution skipped (test/offline environment)
|
|
1298
1309
|
const execEntry = this.debrief.record({
|
|
1299
1310
|
partitionId: deployment.partitionId ?? null,
|
|
1300
|
-
|
|
1311
|
+
operationId: deployment.id,
|
|
1301
1312
|
agent: "server",
|
|
1302
1313
|
decisionType: "deployment-execution",
|
|
1303
1314
|
decision: `Skipped Envoy delegation for ${artifact.name} v${deployment.version} — no settings reader configured`,
|
|
@@ -1355,7 +1366,7 @@ export class SynthAgent {
|
|
|
1355
1366
|
// Envoy is healthy — delegate the deployment
|
|
1356
1367
|
const delegateEntry = this.debrief.record({
|
|
1357
1368
|
partitionId: deployment.partitionId ?? null,
|
|
1358
|
-
|
|
1369
|
+
operationId: deployment.id,
|
|
1359
1370
|
agent: "server",
|
|
1360
1371
|
decisionType: "deployment-execution",
|
|
1361
1372
|
decision: `Delegating execution of ${artifact.name} v${deployment.version} to Envoy at ${envoyConfig.url}`,
|
|
@@ -1382,10 +1393,10 @@ export class SynthAgent {
|
|
|
1382
1393
|
|
|
1383
1394
|
envoyResult = await client.deploy({
|
|
1384
1395
|
deploymentId: deployment.id,
|
|
1396
|
+
operationId: deployment.id,
|
|
1385
1397
|
partitionId: deployment.partitionId ?? "",
|
|
1386
1398
|
environmentId: deployment.environmentId ?? "",
|
|
1387
|
-
|
|
1388
|
-
version: deployment.version,
|
|
1399
|
+
version: deployment.version ?? "",
|
|
1389
1400
|
variables: deployment.variables,
|
|
1390
1401
|
environmentName: environment.name,
|
|
1391
1402
|
partitionName: partition?.name ?? "",
|
|
@@ -1407,7 +1418,7 @@ export class SynthAgent {
|
|
|
1407
1418
|
for (const entry of envoyResult.debriefEntries) {
|
|
1408
1419
|
const ingested = this.debrief.record({
|
|
1409
1420
|
partitionId: entry.partitionId ?? deployment.partitionId ?? null,
|
|
1410
|
-
|
|
1421
|
+
operationId: entry.operationId ?? deployment.id,
|
|
1411
1422
|
agent: entry.agent,
|
|
1412
1423
|
decisionType: entry.decisionType,
|
|
1413
1424
|
decision: entry.decision,
|
|
@@ -1442,7 +1453,7 @@ export class SynthAgent {
|
|
|
1442
1453
|
// Record successful delegation completion
|
|
1443
1454
|
const completionEntry = this.debrief.record({
|
|
1444
1455
|
partitionId: deployment.partitionId ?? null,
|
|
1445
|
-
|
|
1456
|
+
operationId: deployment.id,
|
|
1446
1457
|
agent: "server",
|
|
1447
1458
|
decisionType: "deployment-execution",
|
|
1448
1459
|
decision: `Envoy completed deployment successfully in ${envoyResult.executionDurationMs}ms — ${envoyResult.artifacts.length} artifact(s) produced`,
|
|
@@ -1475,7 +1486,7 @@ export class SynthAgent {
|
|
|
1475
1486
|
): Promise<void> {
|
|
1476
1487
|
const entry = this.debrief.record({
|
|
1477
1488
|
partitionId: deployment.partitionId ?? null,
|
|
1478
|
-
|
|
1489
|
+
operationId: deployment.id,
|
|
1479
1490
|
agent: "server",
|
|
1480
1491
|
decisionType: "deployment-verification",
|
|
1481
1492
|
decision: `Post-deploy verification skipped for ${artifact.name} v${deployment.version} — no Envoy configured`,
|
|
@@ -1506,7 +1517,10 @@ export class SynthAgent {
|
|
|
1506
1517
|
// In-memory deployment store
|
|
1507
1518
|
// ---------------------------------------------------------------------------
|
|
1508
1519
|
|
|
1509
|
-
|
|
1520
|
+
const getArtifactId = (op: Deployment): string =>
|
|
1521
|
+
op.input?.type === "deploy" ? (op.input as { type: "deploy"; artifactId: string }).artifactId : "";
|
|
1522
|
+
|
|
1523
|
+
export class InMemoryDeploymentStore implements OperationStore {
|
|
1510
1524
|
private deployments: Map<DeploymentId, Deployment> = new Map();
|
|
1511
1525
|
|
|
1512
1526
|
save(deployment: Deployment): void {
|
|
@@ -1525,7 +1539,7 @@ export class InMemoryDeploymentStore implements DeploymentStore {
|
|
|
1525
1539
|
|
|
1526
1540
|
getByArtifact(artifactId: string): Deployment[] {
|
|
1527
1541
|
return [...this.deployments.values()].filter(
|
|
1528
|
-
(d) => d
|
|
1542
|
+
(d) => getArtifactId(d) === artifactId,
|
|
1529
1543
|
);
|
|
1530
1544
|
}
|
|
1531
1545
|
|
|
@@ -1542,7 +1556,7 @@ export class InMemoryDeploymentStore implements DeploymentStore {
|
|
|
1542
1556
|
findByArtifactVersion(artifactId: string, version: string, status?: string): Deployment[] {
|
|
1543
1557
|
return [...this.deployments.values()].filter(
|
|
1544
1558
|
(d) =>
|
|
1545
|
-
d
|
|
1559
|
+
getArtifactId(d) === artifactId &&
|
|
1546
1560
|
d.version === version &&
|
|
1547
1561
|
(!status || d.status === status),
|
|
1548
1562
|
);
|
|
@@ -1552,7 +1566,7 @@ export class InMemoryDeploymentStore implements DeploymentStore {
|
|
|
1552
1566
|
return [...this.deployments.values()]
|
|
1553
1567
|
.filter(
|
|
1554
1568
|
(d) =>
|
|
1555
|
-
d
|
|
1569
|
+
getArtifactId(d) === artifactId &&
|
|
1556
1570
|
new Date(d.createdAt).getTime() >= since.getTime() &&
|
|
1557
1571
|
(!status || d.status === status),
|
|
1558
1572
|
)
|