fix(shadow): repair field routing and harden evidence collection
This commit is contained in:
@@ -254,6 +254,41 @@ class Ft546ShadowCycleTest {
|
||||
assertEquals(now, report.getLastCompletedCycle());
|
||||
}
|
||||
|
||||
@Test
|
||||
void evidenceCompletionAndReadyTimeIncludeWorkInsteadOfReusingEntryTime() {
|
||||
Instant now = Instant.parse("2026-09-06T23:59:59Z");
|
||||
java.util.concurrent.atomic.AtomicReference<Instant> time =
|
||||
new java.util.concurrent.atomic.AtomicReference<Instant>(now);
|
||||
Clock clock = new Clock() {
|
||||
public java.time.ZoneId getZone() { return ZoneOffset.UTC; }
|
||||
public Clock withZone(java.time.ZoneId zone) { return this; }
|
||||
public Instant instant() { return time.get(); }
|
||||
};
|
||||
RecordingEvidenceRepository evidence = new RecordingEvidenceRepository();
|
||||
java.util.concurrent.atomic.AtomicLong nanos = new java.util.concurrent.atomic.AtomicLong();
|
||||
ShadowCycleDiagnostics progress = new ShadowCycleDiagnostics(nanos::get);
|
||||
evidence.beforeCommit = () -> {
|
||||
time.set(now.plusSeconds(3));
|
||||
nanos.set(3000000000L);
|
||||
};
|
||||
ShadowStartupReport report = ShadowStartupReport.starting(now, Collections.emptyMap());
|
||||
Ft546ShadowCycle cycle = evidenceCycle(now, evidence, report, gateway(), clock,
|
||||
() -> {
|
||||
time.set(now.plusSeconds(2));
|
||||
nanos.set(2000000000L);
|
||||
});
|
||||
ShadowFactBatch batch = ShadowFactBatch.create(now.minusMillis(50), now,
|
||||
Collections.singletonList(queueWithoutLegacyAssignment(now)),
|
||||
Collections.singletonList(vehicle(now)), maps());
|
||||
assertEquals("COMPARABLE", cycle.run(batch, now, progress).getStatusCode());
|
||||
assertEquals(now.plusSeconds(2), evidence.saved.get(0).getCompletedAt());
|
||||
assertEquals(now.plusSeconds(3), report.getLastCompletedCycle());
|
||||
assertEquals(Long.valueOf(3000), report.getSourceLagMillis());
|
||||
Map<?, ?> timings = (Map<?, ?>) progress.finish("COMPARABLE", null).get("phaseMillis");
|
||||
assertEquals(1000L, timings.get("TRANSACTION_COMMIT"));
|
||||
assertEquals(2000L, timings.get("DISPATCH_AND_STATE_SAVE"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void evidenceFailureRecordsNoCommandAndStopsLaterCalculation() {
|
||||
Instant now = Instant.parse("2026-08-14T06:00:00Z");
|
||||
@@ -345,7 +380,7 @@ class Ft546ShadowCycleTest {
|
||||
ShadowCycleResult second = cycle.run(batch, now.plusSeconds(1));
|
||||
|
||||
assertEquals("DEGRADED", first.getStatusCode());
|
||||
assertEquals("EVIDENCE_STORAGE_UNAVAILABLE", first.getReasonCode());
|
||||
assertEquals("FREE_SPACE_BELOW_MINIMUM", first.getReasonCode());
|
||||
assertEquals("DEGRADED", second.getStatusCode());
|
||||
assertEquals(1, healthChecks.get());
|
||||
assertEquals(0, assemblyCalls.get());
|
||||
@@ -357,6 +392,14 @@ class Ft546ShadowCycleTest {
|
||||
Instant now, RecordingEvidenceRepository evidence,
|
||||
ShadowStartupReport report,
|
||||
RecordingFt546CommandGateway gateway) {
|
||||
return evidenceCycle(now, evidence, report, gateway,
|
||||
Clock.fixed(now, ZoneOffset.UTC), () -> { });
|
||||
}
|
||||
|
||||
private static Ft546ShadowCycle evidenceCycle(
|
||||
Instant now, RecordingEvidenceRepository evidence,
|
||||
ShadowStartupReport report, RecordingFt546CommandGateway gateway,
|
||||
Clock clock, Runnable onDispatch) {
|
||||
RoutePlanner routePlanner = new AStarRoutePlanner();
|
||||
DispatchCoordinator coordinator = new DispatchCoordinator(
|
||||
new DefaultVehicleAssignmentPolicy(routePlanner,
|
||||
@@ -365,6 +408,7 @@ class Ft546ShadowCycleTest {
|
||||
commit -> {
|
||||
evidence.dispatchObservedInsideTransaction =
|
||||
evidence.insideTransaction;
|
||||
onDispatch.run();
|
||||
return true;
|
||||
}, () -> "SHADOW-1");
|
||||
ReadOnlyDispatchCycle<Ft546SrgvCommandIntent> readOnly =
|
||||
@@ -384,7 +428,7 @@ class Ft546ShadowCycleTest {
|
||||
assembler::assemble, readOnly, gateway,
|
||||
() -> ShadowEvidenceStorageHealth.Check.available(
|
||||
Long.MAX_VALUE),
|
||||
evidence, collector(evidence.runId, 3L), report);
|
||||
evidence, collector(evidence.runId, 3L), report, clock);
|
||||
}
|
||||
|
||||
private static ShadowCycleEvidenceCollector collector(
|
||||
@@ -542,6 +586,7 @@ class Ft546ShadowCycleTest {
|
||||
private boolean insideTransaction;
|
||||
private boolean dispatchObservedInsideTransaction;
|
||||
private RuntimeException saveFailure;
|
||||
private Runnable beforeCommit = () -> { };
|
||||
|
||||
@Override
|
||||
public SavedCycle save(ShadowCycleEvidence evidence) {
|
||||
@@ -570,7 +615,9 @@ class Ft546ShadowCycleTest {
|
||||
transactionCalls.incrementAndGet();
|
||||
insideTransaction = true;
|
||||
try {
|
||||
return work.get();
|
||||
T result = work.get();
|
||||
beforeCommit.run();
|
||||
return result;
|
||||
} finally {
|
||||
insideTransaction = false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user