feat(gateway): harden failover and payload handling

Retry pre-response transport failures across candidates with an explicit stop policy, and propagate end-to-end timing into usage records and UI diagnostics.

Remove legacy body, import, cookie, PII, and tunnel replay caps while preserving optional operator-configured gateway limits.
This commit is contained in:
elky
2026-07-30 01:03:27 +08:00
parent a97acc07fc
commit a04673a90d
80 changed files with 3640 additions and 1236 deletions
@@ -127,4 +127,38 @@ mod tests {
assert_eq!(trace.final_status, RequestCandidateFinalStatus::Failed);
assert_eq!(trace.total_latency_ms, 33);
}
#[tokio::test]
async fn request_candidate_trace_recovers_missing_transport_latency_from_timestamps() {
let repository = Arc::new(InMemoryRequestCandidateRepository::seed(vec![
sample_candidate(
"cand-timeout",
"req-failover",
0,
RequestCandidateStatus::Failed,
Some(100),
None,
None,
),
sample_candidate(
"cand-success",
"req-failover",
1,
RequestCandidateStatus::Success,
Some(101),
Some(626),
Some(200),
),
]));
let state = GatewayDataState::with_request_candidate_reader_for_tests(repository);
let trace = read_request_candidate_trace(&state, "req-failover", true)
.await
.expect("trace should succeed")
.expect("trace should exist");
assert_eq!(trace.total_candidates, 2);
assert_eq!(trace.final_status, RequestCandidateFinalStatus::Success);
assert_eq!(trace.total_latency_ms, 1_626);
}
}
@@ -20,8 +20,6 @@ use aether_runtime_state::RuntimeQueueStore;
use aether_usage_runtime::{
UsageBillingEventEnricher, UsageBodyCapturePolicy, UsageEvent, UsageRecordWriter,
UsageRequestRecordLevel, UsageRuntimeAccess, UsageSettlementWriter,
DEFAULT_USAGE_REQUEST_BODY_CAPTURE_LIMIT_BYTES,
DEFAULT_USAGE_RESPONSE_BODY_CAPTURE_LIMIT_BYTES,
};
use aether_video_tasks_core::StoredVideoTaskReadSide;
use async_trait::async_trait;
@@ -33,8 +31,6 @@ use crate::provider_transport::ProviderTransportSnapshotSource;
const REQUEST_RECORD_LEVEL_KEY: &str = "request_record_level";
const LEGACY_REQUEST_LOG_LEVEL_KEY: &str = "request_log_level";
const MAX_REQUEST_BODY_SIZE_KEY: &str = "max_request_body_size";
const MAX_RESPONSE_BODY_SIZE_KEY: &str = "max_response_body_size";
fn usage_request_record_level_from_value(value: Option<&Value>) -> UsageRequestRecordLevel {
let Some(value) = value.and_then(Value::as_str).map(str::trim) else {
@@ -53,14 +49,6 @@ fn usage_request_record_level_from_value(value: Option<&Value>) -> UsageRequestR
}
}
fn usage_body_capture_limit_from_value(value: Option<&Value>, default: usize) -> Option<usize> {
match value.and_then(Value::as_u64) {
Some(0) => None,
Some(limit) => usize::try_from(limit).ok().filter(|limit| *limit > 0),
None => Some(default),
}
}
#[async_trait]
impl RequestAuditReader for GatewayDataState {
async fn find_request_usage_audit_by_request_id(
@@ -282,20 +270,8 @@ impl UsageRuntimeAccess for GatewayDataState {
.await?
}
};
let max_request_body_size =
GatewayDataState::find_system_config_value(self, MAX_REQUEST_BODY_SIZE_KEY).await?;
let max_response_body_size =
GatewayDataState::find_system_config_value(self, MAX_RESPONSE_BODY_SIZE_KEY).await?;
Ok(UsageBodyCapturePolicy {
record_level: usage_request_record_level_from_value(value.as_ref()),
max_request_body_bytes: usage_body_capture_limit_from_value(
max_request_body_size.as_ref(),
DEFAULT_USAGE_REQUEST_BODY_CAPTURE_LIMIT_BYTES,
),
max_response_body_bytes: usage_body_capture_limit_from_value(
max_response_body_size.as_ref(),
DEFAULT_USAGE_RESPONSE_BODY_CAPTURE_LIMIT_BYTES,
),
})
}
}
@@ -502,7 +478,7 @@ mod tests {
}
#[tokio::test]
async fn usage_runtime_access_reads_body_capture_limits_from_system_config() {
async fn usage_runtime_access_ignores_legacy_body_capture_limits() {
let state = GatewayDataState::disabled().with_system_config_values_for_tests([
("max_request_body_size".to_string(), json!(1234)),
("max_response_body_size".to_string(), json!(5678)),
@@ -513,22 +489,5 @@ mod tests {
.expect("body capture policy should read");
assert_eq!(policy.record_level, UsageRequestRecordLevel::Full);
assert_eq!(policy.max_request_body_bytes, Some(1234));
assert_eq!(policy.max_response_body_bytes, Some(5678));
}
#[tokio::test]
async fn usage_runtime_access_treats_zero_body_capture_limit_as_unbounded() {
let state = GatewayDataState::disabled().with_system_config_values_for_tests([
("max_request_body_size".to_string(), json!(0)),
("max_response_body_size".to_string(), json!(0)),
]);
let policy = UsageRuntimeAccess::body_capture_policy(&state)
.await
.expect("body capture policy should read");
assert_eq!(policy.max_request_body_bytes, None);
assert_eq!(policy.max_response_body_bytes, None);
}
}