Skip to content

Commit 918c579

Browse files
committed
refactor: WIP
1 parent 4254a6d commit 918c579

14 files changed

Lines changed: 611 additions & 234 deletions

File tree

cloud/flamingock-cloud/src/main/java/io/flamingock/cloud/planner/CloudExecutionPlanner.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import io.flamingock.internal.util.ThreadSleeper;
2323
import io.flamingock.internal.util.TimeService;
2424
import io.flamingock.internal.common.core.error.FlamingockException;
25+
import io.flamingock.internal.common.core.response.data.PlannerVerdict;
2526
import io.flamingock.cloud.api.request.ExecutionPlanRequest;
2627
import io.flamingock.cloud.api.response.ExecutionPlanResponse;
2728
import io.flamingock.internal.common.core.targets.TargetSystemAuditMarkType;
@@ -35,6 +36,7 @@
3536
import io.flamingock.internal.core.external.store.lock.LockException;
3637
import io.flamingock.internal.core.pipeline.loaded.stage.AbstractLoadedStage;
3738
import io.flamingock.internal.core.pipeline.run.PipelineRun;
39+
import io.flamingock.internal.core.pipeline.run.StageRun;
3840
import io.flamingock.internal.util.log.FlamingockLoggerFactory;
3941
import org.slf4j.Logger;
4042

@@ -102,6 +104,12 @@ public ExecutionPlan getNextExecution(PipelineRun pipelineRun) throws LockExcept
102104
}
103105

104106
if (response.isContinue()) {
107+
//TODO temporally. Remove this
108+
// Server's CONTINUE is authoritative: nothing left to apply for any stage in
109+
// the pipeline. Stamp the planner's UP_TO_DATE verdict on every stage the
110+
// planner hasn't already evaluated this run. No per-change records — the
111+
// server's CONTINUE doesn't carry per-change data and we don't synthesize.
112+
markRemainingStagesUpToDate(pipelineRun);
105113
return ExecutionPlan.CONTINUE();
106114

107115
} else if (response.isExecute()) {
@@ -200,6 +208,22 @@ private ExecutionPlan buildNextExecutionPlan(List<AbstractLoadedStage> loadedSta
200208
);
201209
}
202210

211+
/**
212+
* Stamps {@link PlannerVerdict#UP_TO_DATE} on every still-{@code NOT_EVALUATED} stage in the
213+
* pipeline. Called when the cloud server returns {@code CONTINUE} — its verdict is authoritative
214+
* for the whole pipeline. No per-change records are added; the server's CONTINUE payload doesn't
215+
* carry per-change data and we don't synthesize.
216+
*/
217+
private static void markRemainingStagesUpToDate(PipelineRun pipelineRun) {
218+
for (StageRun stageRun : pipelineRun.getStageRuns()) {
219+
if (stageRun.getResult().getPlannerVerdict() == PlannerVerdict.NOT_EVALUATED
220+
&& !stageRun.getState().isFailed()
221+
&& !stageRun.getState().isCompleted()) {
222+
pipelineRun.markStageVerdict(stageRun.getName(), PlannerVerdict.UP_TO_DATE);
223+
}
224+
}
225+
}
226+
203227
static class AuditMarkSnapshot {
204228
private final Collection<TargetSystemAuditMark> marks;
205229
private final Map<String, TargetSystemAuditMarker> markerByChangeId;

core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ExecuteResponseData.java

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ public class ExecuteResponseData {
3434

3535
// Aggregate counts
3636
private int totalStages;
37-
private int reachedStages;
37+
private int upToDateStages;
38+
private int notReachedStages;
3839
private int completedStages;
3940
private int failedStages;
4041
private int totalChanges;
41-
private int reachedChanges;
4242
private int appliedChanges;
4343
private int skippedChanges;
4444
private int failedChanges;
@@ -56,11 +56,11 @@ private ExecuteResponseData(Builder builder) {
5656
this.endTime = builder.endTime;
5757
this.totalDurationMs = builder.totalDurationMs;
5858
this.totalStages = builder.totalStages;
59-
this.reachedStages = builder.reachedStages;
59+
this.upToDateStages = builder.upToDateStages;
60+
this.notReachedStages = builder.notReachedStages;
6061
this.completedStages = builder.completedStages;
6162
this.failedStages = builder.failedStages;
6263
this.totalChanges = builder.totalChanges;
63-
this.reachedChanges = builder.reachedChanges;
6464
this.appliedChanges = builder.appliedChanges;
6565
this.skippedChanges = builder.skippedChanges;
6666
this.failedChanges = builder.failedChanges;
@@ -107,12 +107,20 @@ public void setTotalStages(int totalStages) {
107107
this.totalStages = totalStages;
108108
}
109109

110-
public int getReachedStages() {
111-
return reachedStages;
110+
public int getUpToDateStages() {
111+
return upToDateStages;
112112
}
113113

114-
public void setReachedStages(int reachedStages) {
115-
this.reachedStages = reachedStages;
114+
public void setUpToDateStages(int upToDateStages) {
115+
this.upToDateStages = upToDateStages;
116+
}
117+
118+
public int getNotReachedStages() {
119+
return notReachedStages;
120+
}
121+
122+
public void setNotReachedStages(int notReachedStages) {
123+
this.notReachedStages = notReachedStages;
116124
}
117125

118126
public int getCompletedStages() {
@@ -139,14 +147,6 @@ public void setTotalChanges(int totalChanges) {
139147
this.totalChanges = totalChanges;
140148
}
141149

142-
public int getReachedChanges() {
143-
return reachedChanges;
144-
}
145-
146-
public void setReachedChanges(int reachedChanges) {
147-
this.reachedChanges = reachedChanges;
148-
}
149-
150150
public int getAppliedChanges() {
151151
return appliedChanges;
152152
}
@@ -197,11 +197,11 @@ public static class Builder {
197197
private Instant endTime;
198198
private long totalDurationMs;
199199
private int totalStages;
200-
private int reachedStages;
200+
private int upToDateStages;
201+
private int notReachedStages;
201202
private int completedStages;
202203
private int failedStages;
203204
private int totalChanges;
204-
private int reachedChanges;
205205
private int appliedChanges;
206206
private int skippedChanges;
207207
private int failedChanges;
@@ -232,8 +232,13 @@ public Builder totalStages(int totalStages) {
232232
return this;
233233
}
234234

235-
public Builder reachedStages(int reachedStages) {
236-
this.reachedStages = reachedStages;
235+
public Builder upToDateStages(int upToDateStages) {
236+
this.upToDateStages = upToDateStages;
237+
return this;
238+
}
239+
240+
public Builder notReachedStages(int notReachedStages) {
241+
this.notReachedStages = notReachedStages;
237242
return this;
238243
}
239244

@@ -252,11 +257,6 @@ public Builder totalChanges(int totalChanges) {
252257
return this;
253258
}
254259

255-
public Builder reachedChanges(int reachedChanges) {
256-
this.reachedChanges = reachedChanges;
257-
return this;
258-
}
259-
260260
public Builder appliedChanges(int appliedChanges) {
261261
this.appliedChanges = appliedChanges;
262262
return this;

core/flamingock-core-commons/src/main/java/io/flamingock/internal/common/core/response/data/ExecutionReportFormatter.java

Lines changed: 48 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -103,39 +103,45 @@ public static String report(ExecuteResponseData result) {
103103

104104
sb.append(" Stages: ")
105105
.append(result.getTotalStages()).append(" total — ")
106-
.append(result.getReachedStages()).append(" reached, ")
107106
.append(result.getCompletedStages()).append(" completed, ")
108-
.append(result.getFailedStages()).append(" failed")
107+
.append(result.getFailedStages()).append(" failed, ")
108+
.append(result.getUpToDateStages()).append(" up to date, ")
109+
.append(result.getNotReachedStages()).append(" not reached")
109110
.append(NEWLINE);
110111
sb.append(" Changes: ")
111112
.append(result.getTotalChanges()).append(" total — ")
112-
.append(result.getReachedChanges()).append(" reached, ")
113113
.append(result.getAppliedChanges()).append(" applied, ")
114-
.append(result.getSkippedChanges()).append(" skipped, ")
114+
.append(result.getSkippedChanges()).append(" already at target state, ")
115115
.append(result.getFailedChanges()).append(" failed")
116116
.append(NEWLINE);
117117

118-
// Per-stage breakdown: only stages the executor actually opened. Stages the planner
119-
// short-circuited past are surfaced separately in the "Not reached" section below (when
120-
// the coverage is partial — a fully up-to-date run shows neither, the headline carries it).
118+
// Per-stage breakdown: include any stage the operation touched (state != NOT_STARTED) OR
119+
// the planner verdicted UP_TO_DATE. Stages with verdict NEEDS_WORK / NOT_EVALUATED and
120+
// still-NOT_STARTED state fall into the "Not reached" section.
121121
List<StageResult> stages = nonNullStages(result);
122-
List<StageResult> reached = stages.stream()
123-
.filter(StageResult::isWasExecuted)
122+
List<StageResult> inBreakdown = stages.stream()
123+
.filter(s -> !s.getState().isNotStarted()
124+
|| s.getPlannerVerdict() == PlannerVerdict.UP_TO_DATE)
124125
.collect(Collectors.toList());
125-
if (!reached.isEmpty()) {
126+
if (!inBreakdown.isEmpty()) {
126127
sb.append(NEWLINE).append(" Per-stage breakdown:").append(NEWLINE).append(NEWLINE);
127-
for (StageResult stage : reached) {
128+
for (StageResult stage : inBreakdown) {
128129
appendStageBlock(sb, stage);
129130
sb.append(NEWLINE);
130131
}
131132
}
132133

133-
// "Not reached" only appears when there's a partial: some stages ran, some didn't. The
134-
// all-up-to-date case (0 reached) is communicated by the NO_CHANGES headline alone.
134+
// "Not reached" lists stages neither operation-touched nor verdicted UP_TO_DATE. Omitted
135+
// when nothing was reached AND nothing was up to date (catastrophic-failure shape; the
136+
// headline conveys it).
135137
List<StageResult> notReached = stages.stream()
136-
.filter(s -> !s.isWasExecuted())
138+
.filter(s -> s.getState().isNotStarted()
139+
&& s.getPlannerVerdict() != PlannerVerdict.UP_TO_DATE)
137140
.collect(Collectors.toList());
138-
if (!notReached.isEmpty() && result.getReachedStages() > 0) {
141+
if (!notReached.isEmpty()
142+
&& (result.getCompletedStages() > 0
143+
|| result.getFailedStages() > 0
144+
|| result.getUpToDateStages() > 0)) {
139145
sb.append(NEWLINE).append(" Not reached (").append(notReached.size()).append("):").append(NEWLINE);
140146
for (StageResult stage : notReached) {
141147
String name = stage.getStageName() != null ? stage.getStageName() : "(unnamed)";
@@ -160,6 +166,26 @@ private static void appendTimeBlock(StringBuilder sb, ExecuteResponseData result
160166
private static void appendStageBlock(StringBuilder sb, StageResult stage) {
161167
String label = stageLabel(stage);
162168
String name = stage.getStageName() != null ? stage.getStageName() : "(unnamed)";
169+
170+
// [UP TO DATE] rows render without a duration (the executor never ran). Community has
171+
// per-change ALREADY_APPLIED records (planner-populated from audit); cloud doesn't —
172+
// we emit either a per-change line or a count-only line depending on what's available.
173+
boolean isUpToDateOnly = stage.getState().isNotStarted()
174+
&& stage.getPlannerVerdict() == PlannerVerdict.UP_TO_DATE;
175+
176+
if (isUpToDateOnly) {
177+
int alreadyAppliedCount = stage.getChanges() != null
178+
? (int) stage.getChanges().stream()
179+
.filter(c -> c != null && c.isAlreadyApplied())
180+
.count()
181+
: 0;
182+
int reportableCount = alreadyAppliedCount > 0 ? alreadyAppliedCount : stage.getTotalChanges();
183+
sb.append(" ").append(label).append(' ').append(name)
184+
.append(" (").append(reportableCount).append(" changes already at target state)")
185+
.append(NEWLINE);
186+
return;
187+
}
188+
163189
sb.append(" ").append(label).append(' ').append(name)
164190
.append(" (").append(stage.getDurationMs()).append(" ms)").append(NEWLINE);
165191

@@ -171,7 +197,7 @@ private static void appendStageBlock(StringBuilder sb, StageResult stage) {
171197
int failed = (int) changes.stream().filter(c -> c != null && (c.isFailed() || c.isRolledBack())).count();
172198
sb.append(" changes: ")
173199
.append(applied).append(" applied, ")
174-
.append(skipped).append(" skipped, ")
200+
.append(skipped).append(" already at target state, ")
175201
.append(failed).append(" failed")
176202
.append(NEWLINE);
177203

@@ -209,6 +235,8 @@ private static void appendErrorBlock(StringBuilder sb, ErrorInfo err) {
209235
}
210236

211237
private static String stageLabel(StageResult stage) {
238+
// Two-dimensional resolution. State (operation-owned) wins whenever it's moved off
239+
// NOT_STARTED. While state is still NOT_STARTED, the planner verdict decides.
212240
StageState state = stage.getState();
213241
if (state == null) {
214242
return "[UNKNOWN] ";
@@ -225,10 +253,11 @@ private static String stageLabel(StageResult stage) {
225253
if (state.isStarted()) {
226254
return "[STARTED] ";
227255
}
228-
if (state.isNotStarted()) {
229-
return "[PENDING] ";
256+
// state.isNotStarted() — verdict decides.
257+
if (stage.getPlannerVerdict() == PlannerVerdict.UP_TO_DATE) {
258+
return "[UP TO DATE]";
230259
}
231-
return "[UNKNOWN] ";
260+
return "[NOT REACHED]";
232261
}
233262

234263
private static String statusLabel(ExecuteResponseData result) {
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright 2026 Flamingock (https://www.flamingock.io)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.flamingock.internal.common.core.response.data;
17+
18+
/**
19+
* The planner's view of a stage, derived from the audit snapshot (community) or the cloud
20+
* server's response. One of two complementary dimensions on {@code StageResult}:
21+
*
22+
* <ul>
23+
* <li>{@code state} ({@link StageState}) — what the operation observed during execution
24+
* (NOT_STARTED → STARTED → COMPLETED / FAILED / BLOCKED_MI). Operation-only.</li>
25+
* <li>{@code plannerVerdict} (this enum) — what the audit/snapshot reflects. Planner-only.</li>
26+
* </ul>
27+
*
28+
* <p>Operation writes are immutable to the planner. The planner sets a verdict only when the
29+
* stage's {@code state} is still {@code NOT_STARTED}; once the operation has reached a terminal
30+
* state, that state is the truth for the run.
31+
*
32+
* <p>Verdict is monotone-forward: NOT_EVALUATED → NEEDS_WORK → UP_TO_DATE. The
33+
* NEEDS_WORK → UP_TO_DATE transition is valid (later snapshot reads see new applies); the
34+
* reverse is not.
35+
*
36+
* <p>Notably absent: a "failed" verdict. Failure is an execution observation owned by
37+
* {@code state}. From the planner's perspective, a stage with a failed change in the audit
38+
* still has pending work — the recovery strategy decides whether to retry.
39+
*/
40+
public enum PlannerVerdict {
41+
42+
/**
43+
* Default. The planner has not reached a determination on this stage. Transient under the
44+
* community planner's always-walk model; persists for stages the planner couldn't evaluate.
45+
*/
46+
NOT_EVALUATED,
47+
48+
/**
49+
* The planner saw at least one change in the stage that is not in an applied state per
50+
* audit. The stage either has work to do or had work that wasn't reached this run.
51+
*/
52+
NEEDS_WORK,
53+
54+
/**
55+
* The planner confirmed every change in the stage is applied per audit. Authoritative —
56+
* the executor was not invoked and does not need to be. Renders as {@code [UP TO DATE]}.
57+
*/
58+
UP_TO_DATE
59+
}

0 commit comments

Comments
 (0)