fix: scope shadow inputs to authoritative warehouse

This commit is contained in:
zgw
2026-09-03 15:40:12 +08:00
parent d77fac1d56
commit 746a6b5647
5 changed files with 208 additions and 45 deletions

View File

@@ -21,7 +21,7 @@ import java.util.function.Supplier;
@Component
public class ShadowPlanningEngine {
private static final int INSTANCE_STALE_SECONDS = 120;
private static final int DECISION_REASON_MAX_LENGTH = 2000;
private static final int DECISION_REASON_MAX_LENGTH = 4000;
private static final String PUBLISHED = "PUBLISHED";
private final SchedulingFacade schedulingFacade;
@@ -128,9 +128,9 @@ public class ShadowPlanningEngine {
+ "; request=" + observation.requestId()
+ "; shadow=" + observation.resultCode()
+ "; proposedSpace=" + nullSafe(observation.proposedSpaceId())
+ "; shadowReason=" + nullSafe(observation.reason())
+ "; legacy=" + nullSafe(legacyResultCode)
+ "; legacySpace=" + nullSafe(legacySpaceId), DECISION_REASON_MAX_LENGTH));
+ "; legacySpace=" + nullSafe(legacySpaceId)
+ "; shadowReason=" + nullSafe(observation.reason()), DECISION_REASON_MAX_LENGTH));
requireComparisonInserted(inserted);
});
}
@@ -149,8 +149,8 @@ public class ShadowPlanningEngine {
+ "; shadow=" + observation.resultCode()
+ "; proposedStock=" + nullSafe(observation.proposedStockId())
+ "; proposedSpace=" + nullSafe(observation.proposedSpaceId())
+ "; shadowReason=" + nullSafe(observation.reason())
+ "; legacy=" + nullSafe(legacyResultCode), DECISION_REASON_MAX_LENGTH));
+ "; legacy=" + nullSafe(legacyResultCode)
+ "; shadowReason=" + nullSafe(observation.reason()), DECISION_REASON_MAX_LENGTH));
requireComparisonInserted(inserted);
});
}

View File

@@ -13,6 +13,7 @@ import com.ruoyi.algorithm.repository.VersionToken;
import com.ruoyi.algorithm.service.InboundPlanningResult;
import com.ruoyi.algorithm.shadow.mapper.ShadowInputMapper;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
@@ -207,20 +208,49 @@ class ShadowPlanningEngineTest {
}
@Test
void comparisonReasonIsBoundedToDecisionLogCapacity() {
void inboundComparisonRetainsRequiredEvidenceWhenUnicodeReasonIsOversized() {
ShadowInputMapper mapper = mock(ShadowInputMapper.class);
when(mapper.insertComparison(any(), any(), any(), any(Long.class),
any(Instant.class), any(), any(), any())).thenReturn(1);
ShadowPlanningEngine engine = engine(mock(SchedulingFacade.class), mapper);
String shadowDiagnostic = "snapshotGeneration=42; snapshotSync=2026-09-03T00:00:00Z"
+ "; sourceSignature=ABC123; project=" + "中".repeat(255)
+ "; shadowCandidates=17; reason=" + "长".repeat(5000);
InboundShadowObservation observation = new InboundShadowObservation(
true, 42L, "01", SNAPSHOT.synchronizedAt(), SNAPSHOT.sourceSignature(),
"JOB-1", "READY", "SPACE-9", "X".repeat(2500));
"JOB-1", "READY", "SPACE-9", shadowDiagnostic);
engine.compareInbound(observation, "READY", "SPACE-9");
engine.compareInbound(observation, "READY", "LEGACY-SPACE-9");
ArgumentCaptor<String> reason = ArgumentCaptor.forClass(String.class);
verify(mapper).insertComparison(any(), any(), any(), any(Long.class),
any(Instant.class), any(), any(),
argThat(reason -> reason.length() <= 2000));
reason.capture());
assertThat(reason.getValue()).hasSizeLessThanOrEqualTo(4000)
.contains("sourceSignature=ABC123", "shadowCandidates=17",
"legacy=READY", "legacySpace=LEGACY-SPACE-9");
}
@Test
void outboundComparisonRetainsLegacyOutcomeAndCandidateEvidenceWhenReasonIsOversized() {
ShadowInputMapper mapper = mock(ShadowInputMapper.class);
when(mapper.insertComparison(any(), any(), any(), any(Long.class),
any(Instant.class), any(), any(), any())).thenReturn(1);
ShadowPlanningEngine engine = engine(mock(SchedulingFacade.class), mapper);
String shadowDiagnostic = "snapshotGeneration=42; snapshotSync=2026-09-03T00:00:00Z"
+ "; sourceSignature=ABC123; project=" + "中".repeat(255)
+ "; shadowMatches=23; reason=" + "长".repeat(5000);
OutboundShadowObservation observation = new OutboundShadowObservation(
true, 42L, "01", SNAPSHOT.synchronizedAt(), SNAPSHOT.sourceSignature(),
"DETAIL-1", "READY", "STOCK-1", "SPACE-9", shadowDiagnostic);
engine.compareOutbound(observation, "10");
ArgumentCaptor<String> reason = ArgumentCaptor.forClass(String.class);
verify(mapper).insertComparison(any(), any(), any(), any(Long.class),
any(Instant.class), any(), any(), reason.capture());
assertThat(reason.getValue()).hasSizeLessThanOrEqualTo(4000)
.contains("sourceSignature=ABC123", "shadowMatches=23", "legacy=10");
}
@Test