@synth-deploy/server 1.1.0 → 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 +5 -10
- package/dist/agent/envoy-client.d.ts.map +1 -1
- package/dist/agent/envoy-client.js +4 -4
- package/dist/agent/envoy-client.js.map +1 -1
- package/dist/agent/synth-agent.d.ts.map +1 -1
- package/dist/agent/synth-agent.js +17 -11
- package/dist/agent/synth-agent.js.map +1 -1
- package/dist/api/operations.d.ts.map +1 -1
- package/dist/api/operations.js +91 -74
- package/dist/api/operations.js.map +1 -1
- package/dist/api/schemas.d.ts +250 -133
- package/dist/api/schemas.d.ts.map +1 -1
- package/dist/api/schemas.js +17 -22
- package/dist/api/schemas.js.map +1 -1
- package/dist/fleet/fleet-executor.js +2 -2
- 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 +16 -2
- package/dist/graph/graph-executor.js.map +1 -1
- package/dist/index.js +45 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/agent/envoy-client.ts +8 -8
- package/src/agent/synth-agent.ts +17 -11
- package/src/api/operations.ts +94 -74
- package/src/api/schemas.ts +18 -22
- package/src/fleet/fleet-executor.ts +2 -2
- package/src/graph/graph-executor.ts +16 -2
- package/src/index.ts +45 -21
- package/tests/composite-operations.test.ts +6 -6
package/dist/api/operations.js
CHANGED
|
@@ -140,16 +140,12 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
140
140
|
// Trigger operations: construct MonitoringDirective from plan, present for approval
|
|
141
141
|
if (dep.input.type === "trigger" && !result.blocked) {
|
|
142
142
|
const triggerInput = dep.input;
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
command: step.action,
|
|
146
|
-
label: step.description,
|
|
147
|
-
parseAs: (step.params?.parseAs === "exitCode" ? "exitCode" : "numeric"),
|
|
148
|
-
}));
|
|
143
|
+
// Use probes from the envoy's trigger planning response (embedded in scriptedPlan reasoning),
|
|
144
|
+
// or fall back to a default probe. The envoy's planTrigger generates these.
|
|
149
145
|
const directive = {
|
|
150
146
|
id: dep.id,
|
|
151
147
|
operationId: dep.id,
|
|
152
|
-
probes:
|
|
148
|
+
probes: [{
|
|
153
149
|
command: "echo 0",
|
|
154
150
|
label: "default-probe",
|
|
155
151
|
parseAs: "numeric",
|
|
@@ -159,7 +155,6 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
159
155
|
condition: triggerInput.condition,
|
|
160
156
|
responseIntent: triggerInput.responseIntent,
|
|
161
157
|
responseType: "maintain",
|
|
162
|
-
responseParameters: triggerInput.parameters,
|
|
163
158
|
environmentId: dep.environmentId,
|
|
164
159
|
partitionId: dep.partitionId,
|
|
165
160
|
status: "active",
|
|
@@ -218,7 +213,7 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
218
213
|
decisionType: "plan-generation",
|
|
219
214
|
decision: `Operation plan blocked — infrastructure prerequisites not met`,
|
|
220
215
|
reasoning: result.blockReason ?? result.plan.reasoning,
|
|
221
|
-
context: { stepCount: result.plan.
|
|
216
|
+
context: { stepCount: result.plan.scriptedPlan.stepSummary.length, envoyId: planningEnvoy.id, blocked: true },
|
|
222
217
|
});
|
|
223
218
|
}
|
|
224
219
|
else {
|
|
@@ -231,9 +226,9 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
231
226
|
operationId: dep.id,
|
|
232
227
|
agent: "envoy",
|
|
233
228
|
decisionType: "plan-generation",
|
|
234
|
-
decision: `Operation plan generated with ${result.plan.
|
|
229
|
+
decision: `Operation plan generated with ${result.plan.scriptedPlan.stepSummary.length} steps`,
|
|
235
230
|
reasoning: result.plan.reasoning,
|
|
236
|
-
context: { stepCount: result.plan.
|
|
231
|
+
context: { stepCount: result.plan.scriptedPlan.stepSummary.length, envoyId: planningEnvoy.id, delta: result.delta },
|
|
237
232
|
});
|
|
238
233
|
}
|
|
239
234
|
}).catch((err) => {
|
|
@@ -331,9 +326,9 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
331
326
|
operationId: deployment.id,
|
|
332
327
|
agent: "envoy",
|
|
333
328
|
decisionType: "plan-generation",
|
|
334
|
-
decision: `Operation plan submitted with ${parsed.data.plan.
|
|
329
|
+
decision: `Operation plan submitted with ${parsed.data.plan.scriptedPlan.stepSummary.length} steps`,
|
|
335
330
|
reasoning: parsed.data.plan.reasoning,
|
|
336
|
-
context: { stepCount: parsed.data.plan.
|
|
331
|
+
context: { stepCount: parsed.data.plan.scriptedPlan.stepSummary.length },
|
|
337
332
|
});
|
|
338
333
|
return reply.status(200).send({ deployment });
|
|
339
334
|
});
|
|
@@ -376,7 +371,7 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
376
371
|
target: { type: "deployment", id: deployment.id },
|
|
377
372
|
details: parsed.data.modifications
|
|
378
373
|
? { modifications: parsed.data.modifications }
|
|
379
|
-
: { planStepCount: deployment.plan?.
|
|
374
|
+
: { planStepCount: deployment.plan?.scriptedPlan.stepSummary.length ?? 0 },
|
|
380
375
|
});
|
|
381
376
|
// Composite operations: execute children sequentially
|
|
382
377
|
if (deployment.input.type === "composite") {
|
|
@@ -530,9 +525,14 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
530
525
|
return reply.status(409).send({ error: "Operation has no plan to modify" });
|
|
531
526
|
}
|
|
532
527
|
// Validate modified plan with envoy if available
|
|
533
|
-
if (envoyClient) {
|
|
528
|
+
if (envoyClient && deployment.plan.scriptedPlan) {
|
|
534
529
|
try {
|
|
535
|
-
const
|
|
530
|
+
const modifiedScript = {
|
|
531
|
+
...deployment.plan.scriptedPlan,
|
|
532
|
+
executionScript: parsed.data.executionScript,
|
|
533
|
+
...(parsed.data.rollbackScript !== undefined ? { rollbackScript: parsed.data.rollbackScript } : {}),
|
|
534
|
+
};
|
|
535
|
+
const validation = await envoyClient.validatePlan(modifiedScript);
|
|
536
536
|
if (!validation.valid) {
|
|
537
537
|
return reply.status(422).send({
|
|
538
538
|
error: "Modified plan failed envoy validation",
|
|
@@ -544,35 +544,20 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
544
544
|
// Envoy unreachable — proceed without validation but note it
|
|
545
545
|
}
|
|
546
546
|
}
|
|
547
|
-
//
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
const old = oldSteps[i];
|
|
554
|
-
const cur = newSteps[i];
|
|
555
|
-
if (!old) {
|
|
556
|
-
diffLines.push(`+ Step ${i + 1} (added): ${cur.action} ${cur.target} — ${cur.description}`);
|
|
557
|
-
}
|
|
558
|
-
else if (!cur) {
|
|
559
|
-
diffLines.push(`- Step ${i + 1} (removed): ${old.action} ${old.target} — ${old.description}`);
|
|
560
|
-
}
|
|
561
|
-
else if (old.action !== cur.action || old.target !== cur.target || old.description !== cur.description) {
|
|
562
|
-
diffLines.push(`~ Step ${i + 1} (changed): ${old.action} ${old.target} → ${cur.action} ${cur.target}`);
|
|
563
|
-
if (old.description !== cur.description) {
|
|
564
|
-
diffLines.push(` was: ${old.description}`);
|
|
565
|
-
diffLines.push(` now: ${cur.description}`);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
}
|
|
569
|
-
const diffFromPreviousPlan = diffLines.length > 0
|
|
570
|
-
? diffLines.join("\n")
|
|
571
|
-
: "Steps reordered or metadata changed (actions and targets unchanged)";
|
|
547
|
+
// Compute diff description
|
|
548
|
+
const oldScript = deployment.plan.scriptedPlan?.executionScript ?? "";
|
|
549
|
+
const newScript = parsed.data.executionScript;
|
|
550
|
+
const diffFromPreviousPlan = oldScript !== newScript
|
|
551
|
+
? "Execution script modified by user"
|
|
552
|
+
: "Plan metadata changed (script unchanged)";
|
|
572
553
|
// Apply modifications
|
|
573
554
|
deployment.plan = {
|
|
574
555
|
...deployment.plan,
|
|
575
|
-
|
|
556
|
+
scriptedPlan: {
|
|
557
|
+
...deployment.plan.scriptedPlan,
|
|
558
|
+
executionScript: parsed.data.executionScript,
|
|
559
|
+
...(parsed.data.rollbackScript !== undefined ? { rollbackScript: parsed.data.rollbackScript } : {}),
|
|
560
|
+
},
|
|
576
561
|
diffFromPreviousPlan,
|
|
577
562
|
};
|
|
578
563
|
deployments.save(deployment);
|
|
@@ -587,7 +572,6 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
587
572
|
reasoning: parsed.data.reason,
|
|
588
573
|
context: {
|
|
589
574
|
modifiedBy: actor,
|
|
590
|
-
stepCount: parsed.data.steps.length,
|
|
591
575
|
reason: parsed.data.reason,
|
|
592
576
|
},
|
|
593
577
|
actor: request.user?.email,
|
|
@@ -596,13 +580,13 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
596
580
|
actor,
|
|
597
581
|
action: "operation.modified",
|
|
598
582
|
target: { type: "deployment", id: deployment.id },
|
|
599
|
-
details: { reason: parsed.data.reason
|
|
583
|
+
details: { reason: parsed.data.reason },
|
|
600
584
|
});
|
|
601
585
|
telemetry.record({
|
|
602
586
|
actor,
|
|
603
587
|
action: "agent.recommendation.overridden",
|
|
604
588
|
target: { type: "deployment", id: deployment.id },
|
|
605
|
-
details: { reason: parsed.data.reason,
|
|
589
|
+
details: { reason: parsed.data.reason, diff: diffFromPreviousPlan },
|
|
606
590
|
});
|
|
607
591
|
return { deployment, modified: true };
|
|
608
592
|
});
|
|
@@ -635,10 +619,9 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
635
619
|
try {
|
|
636
620
|
const validation = await planningClientForValidation.validateRefinementFeedback({
|
|
637
621
|
feedback: parsed.data.feedback,
|
|
638
|
-
|
|
622
|
+
currentPlanSummary: (deployment.plan?.scriptedPlan?.stepSummary ?? []).map((s) => ({
|
|
639
623
|
description: s.description,
|
|
640
|
-
|
|
641
|
-
target: s.target,
|
|
624
|
+
reversible: s.reversible,
|
|
642
625
|
})),
|
|
643
626
|
artifactName: artifact?.name ?? "unknown",
|
|
644
627
|
environmentName: environment?.name ?? "unknown",
|
|
@@ -707,9 +690,9 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
707
690
|
operationId: dep.id,
|
|
708
691
|
agent: "envoy",
|
|
709
692
|
decisionType: "plan-generation",
|
|
710
|
-
decision: `Plan regenerated with user feedback (${result.plan.
|
|
693
|
+
decision: `Plan regenerated with user feedback (${result.plan.scriptedPlan.stepSummary.length} steps)`,
|
|
711
694
|
reasoning: result.plan.reasoning,
|
|
712
|
-
context: { stepCount: result.plan.
|
|
695
|
+
context: { stepCount: result.plan.scriptedPlan.stepSummary.length, envoyId: planningEnvoy.id, refinementFeedback: parsed.data.feedback },
|
|
713
696
|
});
|
|
714
697
|
return { deployment: dep, replanned: true };
|
|
715
698
|
});
|
|
@@ -785,17 +768,17 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
785
768
|
return reply.status(503).send({ error: "No envoy available to generate rollback plan" });
|
|
786
769
|
}
|
|
787
770
|
const environment = deployment.environmentId ? environments.get(deployment.environmentId) : undefined;
|
|
788
|
-
// Build the list of completed steps from execution record (or plan as fallback)
|
|
771
|
+
// Build the list of completed steps from execution record (or plan step summaries as fallback)
|
|
789
772
|
const completedSteps = deployment.executionRecord?.steps.map((s) => ({
|
|
790
773
|
description: s.description,
|
|
791
|
-
action:
|
|
792
|
-
target:
|
|
774
|
+
action: "script-step",
|
|
775
|
+
target: "",
|
|
793
776
|
status: s.status,
|
|
794
777
|
output: s.output ?? s.error,
|
|
795
|
-
})) ?? deployment.plan?.
|
|
778
|
+
})) ?? deployment.plan?.scriptedPlan?.stepSummary.map((s) => ({
|
|
796
779
|
description: s.description,
|
|
797
|
-
action:
|
|
798
|
-
target:
|
|
780
|
+
action: "script-step",
|
|
781
|
+
target: "",
|
|
799
782
|
status: "completed",
|
|
800
783
|
})) ?? [];
|
|
801
784
|
const rollbackClient = new EnvoyClient(targetEnvoy.url);
|
|
@@ -835,7 +818,7 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
835
818
|
reasoning: rollbackPlan.reasoning,
|
|
836
819
|
context: {
|
|
837
820
|
requestedBy: actor,
|
|
838
|
-
stepCount: rollbackPlan.
|
|
821
|
+
stepCount: rollbackPlan.scriptedPlan.stepSummary.length,
|
|
839
822
|
envoyId: targetEnvoy.id,
|
|
840
823
|
deploymentStatus: deployment.status,
|
|
841
824
|
},
|
|
@@ -845,7 +828,7 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
845
828
|
actor,
|
|
846
829
|
action: "deployment.rollback-plan-requested",
|
|
847
830
|
target: { type: "deployment", id: deployment.id },
|
|
848
|
-
details: { stepCount: rollbackPlan.
|
|
831
|
+
details: { stepCount: rollbackPlan.scriptedPlan.stepSummary.length },
|
|
849
832
|
});
|
|
850
833
|
return reply.status(200).send({ deployment, rollbackPlan });
|
|
851
834
|
}
|
|
@@ -890,20 +873,30 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
890
873
|
agent: "server",
|
|
891
874
|
decisionType: "rollback-execution",
|
|
892
875
|
decision: `Rollback execution initiated for ${artifact?.name ?? getArtifactId(deployment)} v${deployment.version}`,
|
|
893
|
-
reasoning: `Rollback requested by ${actor}. Executing ${deployment.rollbackPlan.
|
|
894
|
-
context: { initiatedBy: actor, stepCount: deployment.rollbackPlan.
|
|
876
|
+
reasoning: `Rollback requested by ${actor}. Executing ${deployment.rollbackPlan.scriptedPlan.stepSummary.length} rollback step(s).`,
|
|
877
|
+
context: { initiatedBy: actor, stepCount: deployment.rollbackPlan.scriptedPlan.stepSummary.length },
|
|
895
878
|
actor: request.user?.email,
|
|
896
879
|
});
|
|
897
880
|
telemetry.record({
|
|
898
881
|
actor,
|
|
899
882
|
action: "deployment.rollback-executed",
|
|
900
883
|
target: { type: "deployment", id: deployment.id },
|
|
901
|
-
details: { stepCount: deployment.rollbackPlan.
|
|
884
|
+
details: { stepCount: deployment.rollbackPlan.scriptedPlan.stepSummary.length },
|
|
902
885
|
});
|
|
903
886
|
const rollbackClient = new EnvoyClient(targetEnvoy.url);
|
|
904
887
|
// Execute the rollback plan as if it were a forward plan — it IS a forward plan
|
|
905
888
|
// (just in the reverse direction). Use an empty no-op plan as the "rollback of rollback".
|
|
906
|
-
const emptyPlan = {
|
|
889
|
+
const emptyPlan = {
|
|
890
|
+
scriptedPlan: {
|
|
891
|
+
platform: "bash",
|
|
892
|
+
executionScript: "# No rollback of rollback",
|
|
893
|
+
dryRunScript: null,
|
|
894
|
+
rollbackScript: null,
|
|
895
|
+
reasoning: "No rollback of rollback.",
|
|
896
|
+
stepSummary: [],
|
|
897
|
+
},
|
|
898
|
+
reasoning: "No rollback of rollback.",
|
|
899
|
+
};
|
|
907
900
|
rollbackClient.executeApprovedPlan({
|
|
908
901
|
operationId: deployment.id,
|
|
909
902
|
plan: deployment.rollbackPlan,
|
|
@@ -1059,7 +1052,7 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
1059
1052
|
decisionType: "plan-generation",
|
|
1060
1053
|
decision: `Operation plan blocked — infrastructure prerequisites not met`,
|
|
1061
1054
|
reasoning: result.blockReason ?? result.plan.reasoning,
|
|
1062
|
-
context: { stepCount: result.plan.
|
|
1055
|
+
context: { stepCount: result.plan.scriptedPlan.stepSummary.length, envoyId: planningEnvoy.id, blocked: true },
|
|
1063
1056
|
});
|
|
1064
1057
|
}
|
|
1065
1058
|
else {
|
|
@@ -1071,9 +1064,9 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
1071
1064
|
operationId: dep.id,
|
|
1072
1065
|
agent: "envoy",
|
|
1073
1066
|
decisionType: "plan-generation",
|
|
1074
|
-
decision: `Operation plan generated with ${result.plan.
|
|
1067
|
+
decision: `Operation plan generated with ${result.plan.scriptedPlan.stepSummary.length} steps`,
|
|
1075
1068
|
reasoning: result.plan.reasoning,
|
|
1076
|
-
context: { stepCount: result.plan.
|
|
1069
|
+
context: { stepCount: result.plan.scriptedPlan.stepSummary.length, envoyId: planningEnvoy.id, delta: result.delta },
|
|
1077
1070
|
});
|
|
1078
1071
|
}
|
|
1079
1072
|
}).catch((err) => {
|
|
@@ -1296,7 +1289,7 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
1296
1289
|
id: crypto.randomUUID(),
|
|
1297
1290
|
input: responseType === "deploy"
|
|
1298
1291
|
? { type: "deploy", artifactId: "" }
|
|
1299
|
-
: { type: "maintain", intent: triggerInput.responseIntent
|
|
1292
|
+
: { type: "maintain", intent: triggerInput.responseIntent },
|
|
1300
1293
|
intent: triggerInput.responseIntent,
|
|
1301
1294
|
lineage: triggerOp.id,
|
|
1302
1295
|
triggeredBy: "trigger",
|
|
@@ -1597,9 +1590,9 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
1597
1590
|
operationId: childDep.id,
|
|
1598
1591
|
agent: "envoy",
|
|
1599
1592
|
decisionType: "plan-generation",
|
|
1600
|
-
decision: `Child operation plan generated with ${result.plan.
|
|
1593
|
+
decision: `Child operation plan generated with ${result.plan.scriptedPlan.stepSummary.length} steps`,
|
|
1601
1594
|
reasoning: result.plan.reasoning,
|
|
1602
|
-
context: { stepCount: result.plan.
|
|
1595
|
+
context: { stepCount: result.plan.scriptedPlan.stepSummary.length, envoyId: planningEnvoy.id, parentOperationId: parentOp.id },
|
|
1603
1596
|
});
|
|
1604
1597
|
}
|
|
1605
1598
|
catch (err) {
|
|
@@ -1631,19 +1624,43 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
1631
1624
|
if (!anyFailed) {
|
|
1632
1625
|
// All children planned — build combined summary plan and await approval
|
|
1633
1626
|
const allChildren = childIds.map((id) => deployments.get(id)).filter(Boolean);
|
|
1634
|
-
const
|
|
1635
|
-
if (!c.plan)
|
|
1627
|
+
const combinedStepSummary = allChildren.flatMap((c, idx) => {
|
|
1628
|
+
if (!c.plan?.scriptedPlan)
|
|
1636
1629
|
return [];
|
|
1637
|
-
return c.plan.
|
|
1630
|
+
return c.plan.scriptedPlan.stepSummary.map((step) => ({
|
|
1638
1631
|
...step,
|
|
1639
1632
|
description: `[${idx + 1}/${allChildren.length}: ${c.input.type}] ${step.description}`,
|
|
1640
1633
|
}));
|
|
1641
1634
|
});
|
|
1642
1635
|
const combinedReasoning = allChildren.map((c, idx) => `Step ${idx + 1} (${c.input.type}): ${c.plan?.reasoning ?? "no reasoning"}`).join("\n\n");
|
|
1636
|
+
// Combine child execution scripts into a single composite script
|
|
1637
|
+
const combinedScript = allChildren
|
|
1638
|
+
.map((c, idx) => `# --- Child ${idx + 1}/${allChildren.length}: ${c.input.type} ---\n${c.plan?.scriptedPlan?.executionScript ?? "# no script"}`)
|
|
1639
|
+
.join("\n\n");
|
|
1643
1640
|
const parentDep = deployments.get(parentOp.id);
|
|
1644
1641
|
if (parentDep && parentDep.status === "pending") {
|
|
1645
|
-
parentDep.plan = {
|
|
1646
|
-
|
|
1642
|
+
parentDep.plan = {
|
|
1643
|
+
scriptedPlan: {
|
|
1644
|
+
platform: "bash",
|
|
1645
|
+
executionScript: combinedScript,
|
|
1646
|
+
dryRunScript: null,
|
|
1647
|
+
rollbackScript: null,
|
|
1648
|
+
reasoning: combinedReasoning,
|
|
1649
|
+
stepSummary: combinedStepSummary,
|
|
1650
|
+
},
|
|
1651
|
+
reasoning: combinedReasoning,
|
|
1652
|
+
};
|
|
1653
|
+
parentDep.rollbackPlan = {
|
|
1654
|
+
scriptedPlan: {
|
|
1655
|
+
platform: "bash",
|
|
1656
|
+
executionScript: "# Child operations handle their own rollback",
|
|
1657
|
+
dryRunScript: null,
|
|
1658
|
+
rollbackScript: null,
|
|
1659
|
+
reasoning: "Child operations handle their own rollback",
|
|
1660
|
+
stepSummary: [],
|
|
1661
|
+
},
|
|
1662
|
+
reasoning: "Child operations handle their own rollback",
|
|
1663
|
+
};
|
|
1647
1664
|
parentDep.status = "awaiting_approval";
|
|
1648
1665
|
parentDep.recommendation = computeRecommendation(parentDep, deployments);
|
|
1649
1666
|
deployments.save(parentDep);
|
|
@@ -1654,7 +1671,7 @@ export function registerOperationRoutes(app, deployments, debrief, partitions, e
|
|
|
1654
1671
|
decisionType: "composite-plan-ready",
|
|
1655
1672
|
decision: `All ${allChildren.length} child plans ready — composite awaiting approval`,
|
|
1656
1673
|
reasoning: combinedReasoning,
|
|
1657
|
-
context: { childIds, totalSteps:
|
|
1674
|
+
context: { childIds, totalSteps: combinedStepSummary.length },
|
|
1658
1675
|
});
|
|
1659
1676
|
}
|
|
1660
1677
|
}
|