fix(self-operated): remove business collection deadline limits
This commit is contained in:
+1
-1
@@ -7,7 +7,7 @@ import java.net.URI;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
|
||||
/** An operator-attested exclusive window; it is not a distributed account lock. */
|
||||
/** Exclusive grant identity; externalExclusiveUntil bounds VALIDATION only, not BUSINESS. */
|
||||
@ConfigurationProperties(prefix = "baiyue.integration.lingxing-wms.egress")
|
||||
public record LingxingEgressProperties(@DefaultValue("OFF") CollectionMode collectionMode,
|
||||
@DefaultValue("test") String environmentLabel, String sourceAccount, String externalExclusiveGrantId,
|
||||
|
||||
+7
-5
@@ -11,7 +11,7 @@ import java.time.Instant;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** Local serialization supplements, never replaces, the externally attested window and durable budget. */
|
||||
/** Local serialization supplements the exclusive grant and durable account protection. */
|
||||
@Component
|
||||
public final class LingxingEgressGate {
|
||||
private static final Logger log = LoggerFactory.getLogger(LingxingEgressGate.class);
|
||||
@@ -62,7 +62,7 @@ public final class LingxingEgressGate {
|
||||
|| !hash.equals(permit.requestHash()) || permit.allowedAttempts() != 1 || !permit.exclusiveConfirmed()
|
||||
|| !permit.budgetConsumed() || permit.expiresAt() == null || !clock.instant().isBefore(permit.expiresAt())
|
||||
|| permit.expiresAt().isAfter(clock.instant().plusSeconds(30))
|
||||
|| (!continuousBusiness() && permit.expiresAt().isAfter(properties.externalExclusiveUntil()))) {
|
||||
|| (!business() && permit.expiresAt().isAfter(properties.externalExclusiveUntil()))) {
|
||||
recoverableRatePause = false; paused = true; throw failure("BUDGET_PERMIT_INVALID");
|
||||
}
|
||||
// A new persisted permit proves the coordinator accepted the account's cooldown/budget/root state.
|
||||
@@ -108,11 +108,13 @@ public final class LingxingEgressGate {
|
||||
|| !context.sourceAccount().equals(properties.sourceAccount())) throw failure("EGRESS_CONTEXT_INVALID");
|
||||
if (properties.externalExclusiveGrantId() == null || properties.externalExclusiveGrantId().isBlank()
|
||||
|| !properties.externalExclusiveGrantId().equals(context.exclusiveGrantId())
|
||||
|| (!continuousBusiness() && (properties.externalExclusiveUntil() == null || !clock.instant().isBefore(properties.externalExclusiveUntil())))
|
||||
|| (!business() && (properties.externalExclusiveUntil() == null || !clock.instant().isBefore(properties.externalExclusiveUntil())))
|
||||
|| properties.budgetAuthorityBaseUri() == null) throw failure("EXCLUSIVE_WINDOW_UNCONFIRMED");
|
||||
}
|
||||
private boolean continuousBusiness() {
|
||||
return properties.collectionMode() == CollectionMode.BUSINESS && properties.continuousBusinessCollectionEnabled();
|
||||
private boolean business() {
|
||||
// The account authority validates the original task identity, including manual roots.
|
||||
// BUSINESS has no operator session cutoff; each individual permit remains finite.
|
||||
return properties.collectionMode() == CollectionMode.BUSINESS;
|
||||
}
|
||||
private static boolean identifier(String value, int maximum) {
|
||||
return value != null && value.length() <= maximum && value.matches("[A-Za-z0-9][A-Za-z0-9._:-]*");
|
||||
|
||||
+18
-6
@@ -97,13 +97,27 @@ class LingxingEgressGateTest {
|
||||
.hasMessageContaining("ACCOUNT_PAUSED");
|
||||
assertThat(ledger.authorizations).isEqualTo(1);
|
||||
}
|
||||
@Test void disabledContinuousBusinessStillRequiresUnexpiredGrant() {
|
||||
@Test void manualBusinessWithContinuousFlagOffCrossesOldCutoffButRejectsExpiredSinglePermit() {
|
||||
var gate = new LingxingEgressGate(new LingxingEgressProperties(CollectionMode.BUSINESS, "test",
|
||||
"demo-account", "demo-grant", NOW, URI.create("http://127.0.0.1"), Duration.ofMillis(1200), null, false),
|
||||
ledger, clock, clock::advanceMillis);
|
||||
assertThatThrownBy(() -> gate.execute(businessContext("disabled"), Operation.WAREHOUSES, HASH, h -> true))
|
||||
.hasMessageContaining("EXCLUSIVE_WINDOW_UNCONFIRMED");
|
||||
assertThat(ledger.authorizations).isZero();
|
||||
var manual = new EgressContext("demo-account", CollectionMode.BUSINESS, "6", "selfop-6", "manual-after-cutoff", "demo-grant");
|
||||
assertThat(gate.execute(manual, Operation.OUTBOUND_DETAIL, HASH,
|
||||
h -> { h.startHttp(); return h.metadata(); }).actualHttpAttempts()).isEqualTo(1);
|
||||
assertThat(ledger.completions).singleElement().satisfies(c -> assertThat(c.context()).isEqualTo(manual));
|
||||
ledger.mutation = p -> new AttemptPermit(p.context(), p.operation(), p.requestHash(), 1,
|
||||
clock.instant(), true, true);
|
||||
assertThatThrownBy(() -> gate.execute(new EgressContext("demo-account", CollectionMode.BUSINESS, "6", "selfop-6", "manual-expired-permit", "demo-grant"),
|
||||
Operation.OUTBOUND_DETAIL, HASH, h -> { h.startHttp(); return true; })).hasMessageContaining("BUDGET_PERMIT_INVALID");
|
||||
assertThat(ledger.authorizations).isEqualTo(2);assertThat(ledger.completions).hasSize(1);
|
||||
}
|
||||
@Test void manualBusinessWithoutGrantCutoffStillRejectsPermitExpiringWhileSigning() {
|
||||
var gate = new LingxingEgressGate(new LingxingEgressProperties(CollectionMode.BUSINESS, "test",
|
||||
"demo-account", "demo-grant", null, URI.create("http://127.0.0.1"), Duration.ofMillis(1200), null, false),
|
||||
ledger, clock, clock::advanceMillis);
|
||||
assertThatThrownBy(() -> gate.execute(businessContext("manual-signing-expired"), Operation.OUTBOUND_DETAIL, HASH,
|
||||
h -> { clock.advanceMillis(30000); h.startHttp(); return true; })).hasMessageContaining("ATTEMPT_NOT_SENDABLE");
|
||||
assertThat(ledger.completions).singleElement().satisfies(c -> assertThat(c.actualHttpAttempts()).isZero());
|
||||
}
|
||||
@Test void continuousBusinessRetains1200MillisSpacingAndOneHttpPerPermit() {
|
||||
var gate = new LingxingEgressGate(new LingxingEgressProperties(CollectionMode.BUSINESS, "test",
|
||||
@@ -179,8 +193,6 @@ class LingxingEgressGateTest {
|
||||
assertThatThrownBy(()->gate.execute(context("wrong-purpose"),Operation.WAREHOUSES,HASH,h->true)).hasMessageContaining("EGRESS_CONTEXT_INVALID");
|
||||
assertThatThrownBy(()->gate.execute(new EgressContext("other-account",CollectionMode.BUSINESS,"root","session","wrong-account","demo-grant"),Operation.WAREHOUSES,HASH,h->true)).hasMessageContaining("EGRESS_CONTEXT_INVALID");
|
||||
assertThatThrownBy(()->gate.execute(new EgressContext("demo-account",CollectionMode.BUSINESS,"root","session","wrong-grant","other-grant"),Operation.WAREHOUSES,HASH,h->true)).hasMessageContaining("EXCLUSIVE_WINDOW_UNCONFIRMED");
|
||||
clock.advanceMillis(3600000);
|
||||
assertThatThrownBy(()->gate.execute(businessContext("expired-window"),Operation.WAREHOUSES,HASH,h->true)).hasMessageContaining("EXCLUSIVE_WINDOW_UNCONFIRMED");
|
||||
assertThat(ledger.authorizations).isZero();assertThat(ledger.completions).isEmpty();
|
||||
}
|
||||
@Test void testBusinessUnconfirmedCompletionStillRequiresManualReviewAfterTimePasses() {
|
||||
|
||||
Reference in New Issue
Block a user