fix: preserve required shadow comparison evidence

This commit is contained in:
zgw
2026-09-03 15:52:52 +08:00
parent 746a6b5647
commit 9617038e4c
2 changed files with 136 additions and 59 deletions

View File

@@ -22,6 +22,12 @@ import java.util.function.Supplier;
public class ShadowPlanningEngine {
private static final int INSTANCE_STALE_SECONDS = 120;
private static final int DECISION_REASON_MAX_LENGTH = 4000;
private static final int IDENTIFIER_MAX_LENGTH = 80;
private static final int WIDE_IDENTIFIER_MAX_LENGTH = 255;
private static final int SNAPSHOT_SYNC_MAX_LENGTH = 40;
private static final int SOURCE_SIGNATURE_MAX_LENGTH = 64;
private static final int RESULT_REASON_MAX_LENGTH = 600;
private static final int NUMBER_MAX_LENGTH = 64;
private static final String PUBLISHED = "PUBLISHED";
private final SchedulingFacade schedulingFacade;
@@ -124,13 +130,7 @@ public class ShadowPlanningEngine {
observation.snapshotGeneration(), observation.snapshotSynchronizedAt(),
observation.sourceSignature(),
comparisonCode(observation.proposedSpaceId(), legacySpaceId),
abbreviate("snapshotGeneration=" + observation.snapshotGeneration()
+ "; request=" + observation.requestId()
+ "; shadow=" + observation.resultCode()
+ "; proposedSpace=" + nullSafe(observation.proposedSpaceId())
+ "; legacy=" + nullSafe(legacyResultCode)
+ "; legacySpace=" + nullSafe(legacySpaceId)
+ "; shadowReason=" + nullSafe(observation.reason()), DECISION_REASON_MAX_LENGTH));
inboundComparisonReason(observation, legacyResultCode, legacySpaceId));
requireComparisonInserted(inserted);
});
}
@@ -144,13 +144,7 @@ public class ShadowPlanningEngine {
UUID.randomUUID().toString(), observation.warehouseId(), "OUTBOUND_COMPARE",
observation.snapshotGeneration(), observation.snapshotSynchronizedAt(),
observation.sourceSignature(), "LEGACY_" + nullSafe(legacyResultCode),
abbreviate("snapshotGeneration=" + observation.snapshotGeneration()
+ "; request=" + observation.requestId()
+ "; shadow=" + observation.resultCode()
+ "; proposedStock=" + nullSafe(observation.proposedStockId())
+ "; proposedSpace=" + nullSafe(observation.proposedSpaceId())
+ "; legacy=" + nullSafe(legacyResultCode)
+ "; shadowReason=" + nullSafe(observation.reason()), DECISION_REASON_MAX_LENGTH));
outboundComparisonReason(observation, legacyResultCode));
requireComparisonInserted(inserted);
});
}
@@ -299,42 +293,98 @@ public class ShadowPlanningEngine {
private String outboundDiagnostic(PublishedShadowSnapshot snapshot, String requestId,
ShadowStockReference reference, String resultReason) {
return snapshotDiagnostic(snapshot)
+ "; detail=" + nullSafe(requestId)
+ "; rawWarehouse=" + nullSafe(reference == null ? null : reference.getWarehouseId())
+ "; project=" + nullSafe(reference == null ? null : reference.getProjectNo())
+ "; product=" + nullSafe(reference == null ? null : reference.getProductId())
+ "; tray=" + nullSafe(reference == null ? null : reference.getTrayNo())
+ "; stock=" + nullSafe(reference == null ? null : reference.getStockId())
+ "; space=" + nullSafe(reference == null ? null : reference.getSpaceId())
+ "; requestedQuantity=" + objectOrEmpty(
reference == null ? null : reference.getRequestedQuantity())
+ "; inputSnapshotSync=" + nullSafe(
reference == null ? null : reference.getSnapshotSynchronizedAt())
+ "; shadowMatches=" + numberOrUnknown(
reference == null ? null : reference.getShadowMatchCount())
+ "; reason=" + abbreviate(nullSafe(resultReason), 600);
StringBuilder diagnostic = snapshotDiagnostic(snapshot);
appendField(diagnostic, "detail", requestId, IDENTIFIER_MAX_LENGTH);
appendField(diagnostic, "rawWarehouse", reference == null ? null : reference.getWarehouseId(),
IDENTIFIER_MAX_LENGTH);
appendField(diagnostic, "project", reference == null ? null : reference.getProjectNo(),
WIDE_IDENTIFIER_MAX_LENGTH);
appendField(diagnostic, "product", reference == null ? null : reference.getProductId(),
WIDE_IDENTIFIER_MAX_LENGTH);
appendField(diagnostic, "tray", reference == null ? null : reference.getTrayNo(),
WIDE_IDENTIFIER_MAX_LENGTH);
appendField(diagnostic, "stock", reference == null ? null : reference.getStockId(),
IDENTIFIER_MAX_LENGTH);
appendField(diagnostic, "space", reference == null ? null : reference.getSpaceId(),
IDENTIFIER_MAX_LENGTH);
appendField(diagnostic, "requestedQuantity", objectOrEmpty(
reference == null ? null : reference.getRequestedQuantity()), NUMBER_MAX_LENGTH);
appendField(diagnostic, "inputSnapshotSync",
reference == null ? null : reference.getSnapshotSynchronizedAt(), SNAPSHOT_SYNC_MAX_LENGTH);
appendField(diagnostic, "shadowMatches", numberOrUnknown(
reference == null ? null : reference.getShadowMatchCount()), NUMBER_MAX_LENGTH);
appendField(diagnostic, "reason", resultReason, RESULT_REASON_MAX_LENGTH);
return diagnostic.toString();
}
private String inboundDiagnostic(PublishedShadowSnapshot snapshot, String taskNo, String trayNo,
ShadowInboundReference reference, String resultReason) {
return snapshotDiagnostic(snapshot)
+ "; task=" + nullSafe(taskNo)
+ "; tray=" + nullSafe(trayNo)
+ "; request=" + nullSafe(reference == null ? null : reference.getRequestId())
+ "; rawWarehouse=" + nullSafe(reference == null ? null : reference.getWarehouseId())
+ "; project=" + nullSafe(reference == null ? null : reference.getProjectNo())
+ "; product=" + nullSafe(reference == null ? null : reference.getProductId())
+ "; lot=" + nullSafe(reference == null ? null : reference.getLotNo())
+ "; shadowCandidates=" + numberOrUnknown(
reference == null ? null : reference.getShadowCandidateCount())
+ "; reason=" + abbreviate(nullSafe(resultReason), 600);
StringBuilder diagnostic = snapshotDiagnostic(snapshot);
appendField(diagnostic, "task", taskNo, IDENTIFIER_MAX_LENGTH);
appendField(diagnostic, "tray", trayNo, WIDE_IDENTIFIER_MAX_LENGTH);
appendField(diagnostic, "request", reference == null ? null : reference.getRequestId(),
IDENTIFIER_MAX_LENGTH);
appendField(diagnostic, "rawWarehouse", reference == null ? null : reference.getWarehouseId(),
IDENTIFIER_MAX_LENGTH);
appendField(diagnostic, "project", reference == null ? null : reference.getProjectNo(),
WIDE_IDENTIFIER_MAX_LENGTH);
appendField(diagnostic, "product", reference == null ? null : reference.getProductId(),
WIDE_IDENTIFIER_MAX_LENGTH);
appendField(diagnostic, "lot", reference == null ? null : reference.getLotNo(),
IDENTIFIER_MAX_LENGTH);
appendField(diagnostic, "shadowCandidates", numberOrUnknown(
reference == null ? null : reference.getShadowCandidateCount()), NUMBER_MAX_LENGTH);
appendField(diagnostic, "reason", resultReason, RESULT_REASON_MAX_LENGTH);
return diagnostic.toString();
}
private String snapshotDiagnostic(PublishedShadowSnapshot snapshot) {
return "snapshotGeneration=" + snapshot.generation()
+ "; snapshotSync=" + instantOrEmpty(snapshot.synchronizedAt())
+ "; sourceSignature=" + nullSafe(snapshot.sourceSignature());
private String inboundComparisonReason(InboundShadowObservation observation,
String legacyResultCode, String legacySpaceId) {
StringBuilder reason = new StringBuilder();
appendField(reason, "snapshotGeneration", Long.toString(observation.snapshotGeneration()),
NUMBER_MAX_LENGTH);
appendField(reason, "request", observation.requestId(), IDENTIFIER_MAX_LENGTH);
appendField(reason, "shadow", observation.resultCode(), IDENTIFIER_MAX_LENGTH);
appendField(reason, "proposedSpace", observation.proposedSpaceId(), IDENTIFIER_MAX_LENGTH);
appendField(reason, "legacy", legacyResultCode, IDENTIFIER_MAX_LENGTH);
appendField(reason, "legacySpace", legacySpaceId, IDENTIFIER_MAX_LENGTH);
appendRemainingField(reason, "shadowReason", observation.reason());
return reason.toString();
}
private String outboundComparisonReason(OutboundShadowObservation observation, String legacyResultCode) {
StringBuilder reason = new StringBuilder();
appendField(reason, "snapshotGeneration", Long.toString(observation.snapshotGeneration()),
NUMBER_MAX_LENGTH);
appendField(reason, "request", observation.requestId(), IDENTIFIER_MAX_LENGTH);
appendField(reason, "shadow", observation.resultCode(), IDENTIFIER_MAX_LENGTH);
appendField(reason, "proposedStock", observation.proposedStockId(), IDENTIFIER_MAX_LENGTH);
appendField(reason, "proposedSpace", observation.proposedSpaceId(), IDENTIFIER_MAX_LENGTH);
appendField(reason, "legacy", legacyResultCode, IDENTIFIER_MAX_LENGTH);
appendRemainingField(reason, "shadowReason", observation.reason());
return reason.toString();
}
private StringBuilder snapshotDiagnostic(PublishedShadowSnapshot snapshot) {
StringBuilder diagnostic = new StringBuilder();
appendField(diagnostic, "snapshotGeneration", Long.toString(snapshot.generation()), NUMBER_MAX_LENGTH);
appendField(diagnostic, "snapshotSync", instantOrEmpty(snapshot.synchronizedAt()),
SNAPSHOT_SYNC_MAX_LENGTH);
appendField(diagnostic, "sourceSignature", snapshot.sourceSignature(), SOURCE_SIGNATURE_MAX_LENGTH);
return diagnostic;
}
private void appendField(StringBuilder target, String name, String value, int maxValueLength) {
if (target.length() > 0) {
target.append("; ");
}
target.append(name).append('=').append(abbreviate(nullSafe(value), maxValueLength));
}
private void appendRemainingField(StringBuilder target, String name, String value) {
String prefix = target.length() == 0 ? name + "=" : "; " + name + "=";
int remaining = Math.max(0, DECISION_REASON_MAX_LENGTH - target.length() - prefix.length());
target.append(prefix).append(abbreviate(nullSafe(value), remaining));
}
private void requireUppercaseHexSignature(String value, String description) {
@@ -356,7 +406,15 @@ public class ShadowPlanningEngine {
}
private String abbreviate(String value, int maxLength) {
return value.length() <= maxLength ? value : value.substring(0, maxLength);
if (value.length() <= maxLength) {
return value;
}
int end = maxLength;
if (end > 0 && Character.isHighSurrogate(value.charAt(end - 1))
&& Character.isLowSurrogate(value.charAt(end))) {
end--;
}
return value.substring(0, end);
}
private String comparisonCode(String shadowSpaceId, String legacySpaceId) {