@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.
- package/dist/agent/envoy-client.d.ts +65 -15
- package/dist/agent/envoy-client.d.ts.map +1 -1
- package/dist/agent/envoy-client.js +58 -8
- 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 +59 -50
- 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 +1900 -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 +434 -133
- package/dist/api/schemas.d.ts.map +1 -1
- package/dist/api/schemas.js +53 -25
- 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 +3 -3
- package/dist/fleet/fleet-executor.js.map +1 -1
- package/dist/graph/graph-executor.d.ts.map +1 -1
- package/dist/graph/graph-executor.js +18 -4
- package/dist/graph/graph-executor.js.map +1 -1
- package/dist/index.js +89 -61
- 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 +111 -19
- package/src/agent/stale-deployment-detector.ts +1 -1
- package/src/agent/synth-agent.ts +76 -56
- 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 +2260 -0
- package/src/api/partitions.ts +1 -1
- package/src/api/schemas.ts +59 -27
- package/src/api/system.ts +23 -21
- package/src/artifact-analyzer.ts +2 -2
- package/src/fleet/fleet-executor.ts +3 -3
- package/src/graph/graph-executor.ts +18 -4
- package/src/index.ts +91 -61
- 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
|
@@ -127,16 +127,19 @@ async function testDeploy(
|
|
|
127
127
|
opts.envVars ?? {},
|
|
128
128
|
);
|
|
129
129
|
|
|
130
|
-
const
|
|
130
|
+
const operationInput = {
|
|
131
|
+
type: "deploy" as const,
|
|
131
132
|
artifactId: artifact.id,
|
|
132
133
|
artifactVersionId: opts.version ?? "2.0.0",
|
|
134
|
+
};
|
|
135
|
+
const operationTrigger = {
|
|
133
136
|
partitionId: opts.partitionId ?? partition.id,
|
|
134
137
|
environmentId: env.id,
|
|
135
138
|
triggeredBy: "user" as const,
|
|
136
139
|
...(opts.variables ? { variables: opts.variables } : {}),
|
|
137
140
|
};
|
|
138
141
|
|
|
139
|
-
return agent.
|
|
142
|
+
return agent.triggerOperation(operationInput, operationTrigger);
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
// ---------------------------------------------------------------------------
|
|
@@ -169,7 +172,7 @@ describe("Decision Diary — entry specificity", () => {
|
|
|
169
172
|
variables: { LOG_LEVEL: "error" },
|
|
170
173
|
});
|
|
171
174
|
|
|
172
|
-
const entries = diary.
|
|
175
|
+
const entries = diary.getByOperation(result.id);
|
|
173
176
|
expect(entries.length).toBeGreaterThanOrEqual(5);
|
|
174
177
|
|
|
175
178
|
for (const entry of entries) {
|
|
@@ -191,7 +194,7 @@ describe("Decision Diary — entry specificity", () => {
|
|
|
191
194
|
variables: { LOG_LEVEL: "error" },
|
|
192
195
|
});
|
|
193
196
|
|
|
194
|
-
const entries = diary.
|
|
197
|
+
const entries = diary.getByOperation(result.id);
|
|
195
198
|
|
|
196
199
|
// Reasoning must contain at least one concrete reference
|
|
197
200
|
const genericPhrases = [
|
|
@@ -222,7 +225,7 @@ describe("Decision Diary — entry specificity", () => {
|
|
|
222
225
|
|
|
223
226
|
expect(result.status).toBe("failed");
|
|
224
227
|
|
|
225
|
-
const entries = diary.
|
|
228
|
+
const entries = diary.getByOperation(result.id);
|
|
226
229
|
const failEntry = findDecisions(entries, "Deployment failed")[0];
|
|
227
230
|
|
|
228
231
|
// Failure reasoning must contain recommended action
|
|
@@ -239,7 +242,7 @@ describe("Decision Diary — entry specificity", () => {
|
|
|
239
242
|
variables: { LOG_LEVEL: "debug" },
|
|
240
243
|
});
|
|
241
244
|
|
|
242
|
-
const entries = diary.
|
|
245
|
+
const entries = diary.getByOperation(result.id);
|
|
243
246
|
const conflictEntries = findDecisions(entries, "conflict");
|
|
244
247
|
expect(conflictEntries.length).toBeGreaterThanOrEqual(1);
|
|
245
248
|
|
|
@@ -280,7 +283,7 @@ describe("Decision Diary — orchestration completeness", () => {
|
|
|
280
283
|
|
|
281
284
|
const result = await testDeploy(agent, artifactStore, environmentStore, partitionStore);
|
|
282
285
|
|
|
283
|
-
const entries = diary.
|
|
286
|
+
const entries = diary.getByOperation(result.id);
|
|
284
287
|
const types = entries.map((e) => e.decisionType);
|
|
285
288
|
|
|
286
289
|
expect(types).toContain("artifact-analysis");
|
|
@@ -297,7 +300,7 @@ describe("Decision Diary — orchestration completeness", () => {
|
|
|
297
300
|
|
|
298
301
|
const result = await testDeploy(agent, artifactStore, environmentStore, partitionStore);
|
|
299
302
|
|
|
300
|
-
const entries = diary.
|
|
303
|
+
const entries = diary.getByOperation(result.id);
|
|
301
304
|
const types = entries.map((e) => e.decisionType);
|
|
302
305
|
|
|
303
306
|
// Should have artifact analysis, plan, config, health check (retry), and failure
|
|
@@ -318,16 +321,12 @@ describe("Decision Diary — orchestration completeness", () => {
|
|
|
318
321
|
const partition = partitionStore.create("Acme Corp", { DB_HOST: "prod-db.internal" });
|
|
319
322
|
const env = environmentStore.create("staging", { DB_HOST: "staging-db.internal" });
|
|
320
323
|
|
|
321
|
-
const
|
|
322
|
-
artifactId: artifact.id,
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
environmentId: env.id,
|
|
326
|
-
triggeredBy: "user" as const,
|
|
327
|
-
};
|
|
328
|
-
const result = await agent.triggerDeployment(trigger);
|
|
324
|
+
const result = await agent.triggerOperation(
|
|
325
|
+
{ type: "deploy", artifactId: artifact.id, artifactVersionId: "2.0.0" },
|
|
326
|
+
{ partitionId: partition.id, environmentId: env.id, triggeredBy: "user" },
|
|
327
|
+
);
|
|
329
328
|
|
|
330
|
-
const entries = diary.
|
|
329
|
+
const entries = diary.getByOperation(result.id);
|
|
331
330
|
const types = entries.map((e) => e.decisionType);
|
|
332
331
|
|
|
333
332
|
expect(types).toContain("variable-conflict");
|
|
@@ -394,8 +393,8 @@ describe("Decision Diary — retrieval dimensions", () => {
|
|
|
394
393
|
const result1 = await testDeploy(agent, artifactStore, environmentStore, partitionStore, { version: "1.0.0" });
|
|
395
394
|
const result2 = await testDeploy(agent, artifactStore, environmentStore, partitionStore, { version: "2.0.0" });
|
|
396
395
|
|
|
397
|
-
const entries1 = diary.
|
|
398
|
-
const entries2 = diary.
|
|
396
|
+
const entries1 = diary.getByOperation(result1.id);
|
|
397
|
+
const entries2 = diary.getByOperation(result2.id);
|
|
399
398
|
|
|
400
399
|
// Each deployment has its own entries
|
|
401
400
|
expect(entries1.length).toBeGreaterThanOrEqual(5);
|
|
@@ -403,10 +402,10 @@ describe("Decision Diary — retrieval dimensions", () => {
|
|
|
403
402
|
|
|
404
403
|
// No cross-contamination
|
|
405
404
|
for (const e of entries1) {
|
|
406
|
-
expect(e.
|
|
405
|
+
expect(e.operationId).toBe(result1.id);
|
|
407
406
|
}
|
|
408
407
|
for (const e of entries2) {
|
|
409
|
-
expect(e.
|
|
408
|
+
expect(e.operationId).toBe(result2.id);
|
|
410
409
|
}
|
|
411
410
|
});
|
|
412
411
|
|
|
@@ -421,14 +420,14 @@ describe("Decision Diary — retrieval dimensions", () => {
|
|
|
421
420
|
const artifactA = seedArtifact(artifactStore, "app-a");
|
|
422
421
|
const artifactB = seedArtifact(artifactStore, "app-b");
|
|
423
422
|
|
|
424
|
-
await agent.
|
|
425
|
-
artifactId: artifactA.id, artifactVersionId: "1.0.0",
|
|
426
|
-
partitionId: partA.id, environmentId: envA.id, triggeredBy: "user",
|
|
427
|
-
|
|
428
|
-
await agent.
|
|
429
|
-
artifactId: artifactB.id, artifactVersionId: "1.0.0",
|
|
430
|
-
partitionId: partB.id, environmentId: envB.id, triggeredBy: "user",
|
|
431
|
-
|
|
423
|
+
await agent.triggerOperation(
|
|
424
|
+
{ type: "deploy", artifactId: artifactA.id, artifactVersionId: "1.0.0" },
|
|
425
|
+
{ partitionId: partA.id, environmentId: envA.id, triggeredBy: "user" },
|
|
426
|
+
);
|
|
427
|
+
await agent.triggerOperation(
|
|
428
|
+
{ type: "deploy", artifactId: artifactB.id, artifactVersionId: "1.0.0" },
|
|
429
|
+
{ partitionId: partB.id, environmentId: envB.id, triggeredBy: "user" },
|
|
430
|
+
);
|
|
432
431
|
|
|
433
432
|
const entriesA = diary.getByPartition(partA.id);
|
|
434
433
|
const entriesB = diary.getByPartition(partB.id);
|
|
@@ -550,7 +549,7 @@ describe("PersistentDecisionDebrief — SQLite backing store", () => {
|
|
|
550
549
|
it("persists entries across close and reopen", () => {
|
|
551
550
|
const entry = diary.record({
|
|
552
551
|
partitionId: "partition-1",
|
|
553
|
-
|
|
552
|
+
operationId: "deploy-1",
|
|
554
553
|
agent: "command",
|
|
555
554
|
decisionType: "pipeline-plan",
|
|
556
555
|
decision: "Planned deployment pipeline: resolve → execute → verify",
|
|
@@ -567,7 +566,7 @@ describe("PersistentDecisionDebrief — SQLite backing store", () => {
|
|
|
567
566
|
expect(retrieved!.decision).toBe(entry.decision);
|
|
568
567
|
expect(retrieved!.reasoning).toBe(entry.reasoning);
|
|
569
568
|
expect(retrieved!.partitionId).toBe("partition-1");
|
|
570
|
-
expect(retrieved!.
|
|
569
|
+
expect(retrieved!.operationId).toBe("deploy-1");
|
|
571
570
|
expect(retrieved!.decisionType).toBe("pipeline-plan");
|
|
572
571
|
expect(retrieved!.context).toEqual({ artifactId: "web-app", version: "1.0.0" });
|
|
573
572
|
diary2.close();
|
|
@@ -576,7 +575,7 @@ describe("PersistentDecisionDebrief — SQLite backing store", () => {
|
|
|
576
575
|
it("retrieval by deployment returns correct entries", () => {
|
|
577
576
|
diary.record({
|
|
578
577
|
partitionId: "t1",
|
|
579
|
-
|
|
578
|
+
operationId: "d1",
|
|
580
579
|
agent: "command",
|
|
581
580
|
decisionType: "pipeline-plan",
|
|
582
581
|
decision: "Planned pipeline for d1",
|
|
@@ -584,7 +583,7 @@ describe("PersistentDecisionDebrief — SQLite backing store", () => {
|
|
|
584
583
|
});
|
|
585
584
|
diary.record({
|
|
586
585
|
partitionId: "t1",
|
|
587
|
-
|
|
586
|
+
operationId: "d2",
|
|
588
587
|
agent: "command",
|
|
589
588
|
decisionType: "pipeline-plan",
|
|
590
589
|
decision: "Planned pipeline for d2",
|
|
@@ -592,28 +591,28 @@ describe("PersistentDecisionDebrief — SQLite backing store", () => {
|
|
|
592
591
|
});
|
|
593
592
|
diary.record({
|
|
594
593
|
partitionId: "t1",
|
|
595
|
-
|
|
594
|
+
operationId: "d1",
|
|
596
595
|
agent: "command",
|
|
597
596
|
decisionType: "deployment-completion",
|
|
598
597
|
decision: "Deployment d1 completed",
|
|
599
598
|
reasoning: "All steps passed.",
|
|
600
599
|
});
|
|
601
600
|
|
|
602
|
-
const d1Entries = diary.
|
|
601
|
+
const d1Entries = diary.getByOperation("d1");
|
|
603
602
|
expect(d1Entries).toHaveLength(2);
|
|
604
603
|
for (const e of d1Entries) {
|
|
605
|
-
expect(e.
|
|
604
|
+
expect(e.operationId).toBe("d1");
|
|
606
605
|
}
|
|
607
606
|
|
|
608
|
-
const d2Entries = diary.
|
|
607
|
+
const d2Entries = diary.getByOperation("d2");
|
|
609
608
|
expect(d2Entries).toHaveLength(1);
|
|
610
|
-
expect(d2Entries[0].
|
|
609
|
+
expect(d2Entries[0].operationId).toBe("d2");
|
|
611
610
|
});
|
|
612
611
|
|
|
613
612
|
it("retrieval by partition returns correct entries", () => {
|
|
614
613
|
diary.record({
|
|
615
614
|
partitionId: "acme",
|
|
616
|
-
|
|
615
|
+
operationId: "d1",
|
|
617
616
|
agent: "command",
|
|
618
617
|
decisionType: "pipeline-plan",
|
|
619
618
|
decision: "Acme deployment plan",
|
|
@@ -621,7 +620,7 @@ describe("PersistentDecisionDebrief — SQLite backing store", () => {
|
|
|
621
620
|
});
|
|
622
621
|
diary.record({
|
|
623
622
|
partitionId: "beta",
|
|
624
|
-
|
|
623
|
+
operationId: "d2",
|
|
625
624
|
agent: "command",
|
|
626
625
|
decisionType: "pipeline-plan",
|
|
627
626
|
decision: "Beta deployment plan",
|
|
@@ -640,7 +639,7 @@ describe("PersistentDecisionDebrief — SQLite backing store", () => {
|
|
|
640
639
|
it("retrieval by decision type filters correctly", () => {
|
|
641
640
|
diary.record({
|
|
642
641
|
partitionId: "t1",
|
|
643
|
-
|
|
642
|
+
operationId: "d1",
|
|
644
643
|
agent: "command",
|
|
645
644
|
decisionType: "health-check",
|
|
646
645
|
decision: "Health check passed",
|
|
@@ -648,7 +647,7 @@ describe("PersistentDecisionDebrief — SQLite backing store", () => {
|
|
|
648
647
|
});
|
|
649
648
|
diary.record({
|
|
650
649
|
partitionId: "t1",
|
|
651
|
-
|
|
650
|
+
operationId: "d1",
|
|
652
651
|
agent: "command",
|
|
653
652
|
decisionType: "variable-conflict",
|
|
654
653
|
decision: "LOG_LEVEL conflict resolved",
|
|
@@ -656,7 +655,7 @@ describe("PersistentDecisionDebrief — SQLite backing store", () => {
|
|
|
656
655
|
});
|
|
657
656
|
diary.record({
|
|
658
657
|
partitionId: "t1",
|
|
659
|
-
|
|
658
|
+
operationId: "d1",
|
|
660
659
|
agent: "command",
|
|
661
660
|
decisionType: "health-check",
|
|
662
661
|
decision: "Post-flight check passed",
|
|
@@ -678,7 +677,7 @@ describe("PersistentDecisionDebrief — SQLite backing store", () => {
|
|
|
678
677
|
const before = new Date();
|
|
679
678
|
diary.record({
|
|
680
679
|
partitionId: "t1",
|
|
681
|
-
|
|
680
|
+
operationId: "d1",
|
|
682
681
|
agent: "command",
|
|
683
682
|
decisionType: "pipeline-plan",
|
|
684
683
|
decision: "First entry",
|
|
@@ -686,7 +685,7 @@ describe("PersistentDecisionDebrief — SQLite backing store", () => {
|
|
|
686
685
|
});
|
|
687
686
|
diary.record({
|
|
688
687
|
partitionId: "t1",
|
|
689
|
-
|
|
688
|
+
operationId: "d1",
|
|
690
689
|
agent: "command",
|
|
691
690
|
decisionType: "deployment-completion",
|
|
692
691
|
decision: "Second entry",
|
|
@@ -709,7 +708,7 @@ describe("PersistentDecisionDebrief — SQLite backing store", () => {
|
|
|
709
708
|
for (let i = 0; i < 5; i++) {
|
|
710
709
|
diary.record({
|
|
711
710
|
partitionId: "t1",
|
|
712
|
-
|
|
711
|
+
operationId: `d${i}`,
|
|
713
712
|
agent: "command",
|
|
714
713
|
decisionType: "pipeline-plan",
|
|
715
714
|
decision: `Entry ${i}`,
|
|
@@ -731,7 +730,7 @@ describe("PersistentDecisionDebrief — SQLite backing store", () => {
|
|
|
731
730
|
it("context round-trips through JSON correctly", () => {
|
|
732
731
|
const entry = diary.record({
|
|
733
732
|
partitionId: "t1",
|
|
734
|
-
|
|
733
|
+
operationId: "d1",
|
|
735
734
|
agent: "command",
|
|
736
735
|
decisionType: "health-check",
|
|
737
736
|
decision: "Health check with complex context",
|
|
@@ -800,14 +799,14 @@ describe("PersistentDecisionDebrief — integration with SynthAgent", () => {
|
|
|
800
799
|
expect(result.status).toBe("succeeded");
|
|
801
800
|
|
|
802
801
|
// Verify entries exist before close
|
|
803
|
-
const entriesBefore = diary.
|
|
802
|
+
const entriesBefore = diary.getByOperation(result.id);
|
|
804
803
|
expect(entriesBefore.length).toBeGreaterThanOrEqual(5);
|
|
805
804
|
|
|
806
805
|
diary.close();
|
|
807
806
|
|
|
808
807
|
// Reopen and verify persistence
|
|
809
808
|
const diary2 = new PersistentDecisionDebrief(dbPath);
|
|
810
|
-
const entriesAfter = diary2.
|
|
809
|
+
const entriesAfter = diary2.getByOperation(result.id);
|
|
811
810
|
expect(entriesAfter).toHaveLength(entriesBefore.length);
|
|
812
811
|
|
|
813
812
|
// Verify key decision types from the pipeline are present
|
|
@@ -832,18 +831,18 @@ describe("PersistentDecisionDebrief — integration with SynthAgent", () => {
|
|
|
832
831
|
const artA = seedArtifact(artifactStore, "app-a");
|
|
833
832
|
const artB = seedArtifact(artifactStore, "app-b");
|
|
834
833
|
|
|
835
|
-
const result1 = await agent.
|
|
836
|
-
artifactId: artA.id, artifactVersionId: "1.0.0",
|
|
837
|
-
partitionId: partA.id, environmentId: envA.id, triggeredBy: "user",
|
|
838
|
-
|
|
839
|
-
const result2 = await agent.
|
|
840
|
-
artifactId: artB.id, artifactVersionId: "1.0.0",
|
|
841
|
-
partitionId: partB.id, environmentId: envB.id, triggeredBy: "user",
|
|
842
|
-
|
|
834
|
+
const result1 = await agent.triggerOperation(
|
|
835
|
+
{ type: "deploy", artifactId: artA.id, artifactVersionId: "1.0.0" },
|
|
836
|
+
{ partitionId: partA.id, environmentId: envA.id, triggeredBy: "user" },
|
|
837
|
+
);
|
|
838
|
+
const result2 = await agent.triggerOperation(
|
|
839
|
+
{ type: "deploy", artifactId: artB.id, artifactVersionId: "1.0.0" },
|
|
840
|
+
{ partitionId: partB.id, environmentId: envB.id, triggeredBy: "user" },
|
|
841
|
+
);
|
|
843
842
|
|
|
844
843
|
// By deployment
|
|
845
|
-
const acmeEntries = diary.
|
|
846
|
-
const betaEntries = diary.
|
|
844
|
+
const acmeEntries = diary.getByOperation(result1.id);
|
|
845
|
+
const betaEntries = diary.getByOperation(result2.id);
|
|
847
846
|
expect(acmeEntries.length).toBeGreaterThanOrEqual(5);
|
|
848
847
|
expect(betaEntries.length).toBeGreaterThanOrEqual(5);
|
|
849
848
|
|
|
@@ -879,7 +878,7 @@ describe("Decision Diary — human-readable format", () => {
|
|
|
879
878
|
id: "abc-123-def-456",
|
|
880
879
|
timestamp: new Date("2026-02-23T14:30:05.000Z"),
|
|
881
880
|
partitionId: "partition-acme",
|
|
882
|
-
|
|
881
|
+
operationId: "deploy-789",
|
|
883
882
|
agent: "command",
|
|
884
883
|
decisionType: "health-check",
|
|
885
884
|
decision: "Pre-flight health check passed",
|
|
@@ -905,7 +904,7 @@ describe("Decision Diary — human-readable format", () => {
|
|
|
905
904
|
id: "sys-001",
|
|
906
905
|
timestamp: new Date("2026-02-23T12:00:00.000Z"),
|
|
907
906
|
partitionId: null,
|
|
908
|
-
|
|
907
|
+
operationId: null,
|
|
909
908
|
agent: "command",
|
|
910
909
|
decisionType: "system",
|
|
911
910
|
decision: "Command initialized with demo data",
|
|
@@ -925,7 +924,7 @@ describe("Decision Diary — human-readable format", () => {
|
|
|
925
924
|
id: "e1",
|
|
926
925
|
timestamp: new Date("2026-02-23T14:00:00.000Z"),
|
|
927
926
|
partitionId: "t1",
|
|
928
|
-
|
|
927
|
+
operationId: "d1",
|
|
929
928
|
agent: "command",
|
|
930
929
|
decisionType: "pipeline-plan",
|
|
931
930
|
decision: "Entry one",
|
|
@@ -936,7 +935,7 @@ describe("Decision Diary — human-readable format", () => {
|
|
|
936
935
|
id: "e2",
|
|
937
936
|
timestamp: new Date("2026-02-23T14:01:00.000Z"),
|
|
938
937
|
partitionId: "t1",
|
|
939
|
-
|
|
938
|
+
operationId: "d1",
|
|
940
939
|
agent: "command",
|
|
941
940
|
decisionType: "deployment-completion",
|
|
942
941
|
decision: "Entry two",
|
|
@@ -141,14 +141,10 @@ async function testDeploy(
|
|
|
141
141
|
forceInsertEnvironment(stores.environments, e);
|
|
142
142
|
forceInsertArtifact(stores.artifacts, DEFAULT_ARTIFACT_ID, "web-app");
|
|
143
143
|
|
|
144
|
-
return agent.
|
|
145
|
-
artifactId: DEFAULT_ARTIFACT_ID,
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
partitionId: effectivePartition.id,
|
|
149
|
-
triggeredBy: "user",
|
|
150
|
-
variables: oldTrigger.variables,
|
|
151
|
-
});
|
|
144
|
+
return agent.triggerOperation(
|
|
145
|
+
{ type: "deploy", artifactId: DEFAULT_ARTIFACT_ID, artifactVersionId: version },
|
|
146
|
+
{ environmentId: e.id, partitionId: effectivePartition.id, triggeredBy: "user", variables: oldTrigger.variables },
|
|
147
|
+
);
|
|
152
148
|
}
|
|
153
149
|
|
|
154
150
|
// ---------------------------------------------------------------------------
|
|
@@ -190,7 +186,7 @@ describe("Simulated Postmortem — failed deployment read experience", () => {
|
|
|
190
186
|
|
|
191
187
|
expect(deployment.status).toBe("failed");
|
|
192
188
|
|
|
193
|
-
const entries = diary.
|
|
189
|
+
const entries = diary.getByOperation(deployment.id);
|
|
194
190
|
const postmortem = generatePostmortem(entries, deployment);
|
|
195
191
|
|
|
196
192
|
// 1. Reviewer can identify WHAT was being deployed
|
|
@@ -241,7 +237,7 @@ describe("Simulated Postmortem — failed deployment read experience", () => {
|
|
|
241
237
|
|
|
242
238
|
const deployment = await testDeploy(agent, stores, {});
|
|
243
239
|
|
|
244
|
-
const entries = diary.
|
|
240
|
+
const entries = diary.getByOperation(deployment.id);
|
|
245
241
|
const postmortem = generatePostmortem(entries, deployment);
|
|
246
242
|
|
|
247
243
|
// The postmortem should explain that DNS failures don't benefit from retry
|
|
@@ -284,7 +280,7 @@ describe("Simulated Postmortem — failed deployment read experience", () => {
|
|
|
284
280
|
|
|
285
281
|
expect(deployment.status).toBe("failed");
|
|
286
282
|
|
|
287
|
-
const entries = diary.
|
|
283
|
+
const entries = diary.getByOperation(deployment.id);
|
|
288
284
|
const postmortem = generatePostmortem(entries, deployment);
|
|
289
285
|
|
|
290
286
|
// Should explain the configuration block
|
|
@@ -313,7 +309,7 @@ describe("Simulated Postmortem — failed deployment read experience", () => {
|
|
|
313
309
|
|
|
314
310
|
expect(deployment.status).toBe("succeeded");
|
|
315
311
|
|
|
316
|
-
const entries = diary.
|
|
312
|
+
const entries = diary.getByOperation(deployment.id);
|
|
317
313
|
const postmortem = generatePostmortem(entries, deployment);
|
|
318
314
|
|
|
319
315
|
expect(postmortem.summary).toContain("SUCCEEDED");
|
|
@@ -333,7 +329,7 @@ describe("Simulated Postmortem — failed deployment read experience", () => {
|
|
|
333
329
|
|
|
334
330
|
expect(deployment.status).toBe("succeeded");
|
|
335
331
|
|
|
336
|
-
const entries = diary.
|
|
332
|
+
const entries = diary.getByOperation(deployment.id);
|
|
337
333
|
const postmortem = generatePostmortem(entries, deployment);
|
|
338
334
|
|
|
339
335
|
// Timeline should show the retry decision chain
|
|
@@ -368,7 +364,7 @@ describe("Simulated Postmortem — failed deployment read experience", () => {
|
|
|
368
364
|
makeEnvironment({ name: "staging" }),
|
|
369
365
|
);
|
|
370
366
|
|
|
371
|
-
const entries = diary.
|
|
367
|
+
const entries = diary.getByOperation(deployment.id);
|
|
372
368
|
const postmortem = generatePostmortem(entries, deployment);
|
|
373
369
|
|
|
374
370
|
const text = postmortem.formatted;
|
|
@@ -724,7 +720,7 @@ describe("Postmortem report — structural guarantees", () => {
|
|
|
724
720
|
|
|
725
721
|
const deployment = await testDeploy(agent, stores, {});
|
|
726
722
|
|
|
727
|
-
const entries = diary.
|
|
723
|
+
const entries = diary.getByOperation(deployment.id);
|
|
728
724
|
const postmortem = generatePostmortem(entries, deployment);
|
|
729
725
|
|
|
730
726
|
for (let i = 1; i < postmortem.timeline.length; i++) {
|
|
@@ -747,7 +743,7 @@ describe("Postmortem report — structural guarantees", () => {
|
|
|
747
743
|
|
|
748
744
|
const deployment = await testDeploy(agent, stores, { variables: { LOG_LEVEL: "debug" } }, partition, env);
|
|
749
745
|
|
|
750
|
-
const entries = diary.
|
|
746
|
+
const entries = diary.getByOperation(deployment.id);
|
|
751
747
|
const postmortem = generatePostmortem(entries, deployment);
|
|
752
748
|
|
|
753
749
|
// LOG_LEVEL has three-way conflict (env → partition → trigger), total vars = 3
|
|
@@ -760,7 +756,7 @@ describe("Postmortem report — structural guarantees", () => {
|
|
|
760
756
|
|
|
761
757
|
const deployment = await testDeploy(agent, stores, {});
|
|
762
758
|
|
|
763
|
-
const entries = diary.
|
|
759
|
+
const entries = diary.getByOperation(deployment.id);
|
|
764
760
|
const postmortem = generatePostmortem(entries, deployment);
|
|
765
761
|
|
|
766
762
|
expect(postmortem.failureAnalysis).toBeNull();
|
|
@@ -771,7 +767,7 @@ describe("Postmortem report — structural guarantees", () => {
|
|
|
771
767
|
|
|
772
768
|
const deployment = await testDeploy(agent, stores, {});
|
|
773
769
|
|
|
774
|
-
const entries = diary.
|
|
770
|
+
const entries = diary.getByOperation(deployment.id);
|
|
775
771
|
const postmortem = generatePostmortem(entries, deployment);
|
|
776
772
|
|
|
777
773
|
expect(postmortem.failureAnalysis).not.toBeNull();
|
package/tests/mcp-tools.test.ts
CHANGED
|
@@ -207,7 +207,7 @@ describe("get-deployment-status", () => {
|
|
|
207
207
|
});
|
|
208
208
|
|
|
209
209
|
expect(result.isError).toBe(true);
|
|
210
|
-
expect(result.content[0].text).toContain("
|
|
210
|
+
expect(result.content[0].text).toContain("Operation not found");
|
|
211
211
|
expect(result.content[0].text).toContain("does-not-exist");
|
|
212
212
|
});
|
|
213
213
|
});
|