Files
3a2ccbc5e6 perf(usage): query insights by card and selected metric (#556)
* fix(usage): keep optional concurrency scans within a bounded budget

Skip unused concurrency work for Key usage and Dashboard trends. Preserve
other insights when exact concurrency exceeds its row, time, or slot budget.

Constraint: Preserve exact overlap semantics and one snapshot for usage metrics
Rejected: Add hourly peaks or truncate intervals | Neither preserves exact concurrency
Confidence: medium
Scope-risk: moderate
Directive: Keep unavailable concurrency distinct from a measured zero
Tested: Rust 1.99.1 rustfmt on changed Rust files; git diff --check; independent source review
Not-tested: PostgreSQL integration, Clippy, and frontend checks pending isolated CI
Co-authored-by: Codex <noreply@openai.com>

* refactor(analytics): limit concurrency changes to unused results

Keep Key usage and Dashboard skip behavior while retaining the existing concurrency calculation and query budget.

Co-authored-by: Codex <noreply@openai.com>

* perf(usage): query insight cards by selected metric

Split health, performance and cost reads by card and selected metric, with independent frontend requests and metric-aware caching. Only the load metric reads concurrency.

Co-authored-by: Codex <noreply@openai.com>

---------

Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: Zyy <zyycn@users.noreply.github.com>
2026-10-30 15:09:24 +08:00

2484 lines
90 KiB
Rust

//! 验证执行记录端口的请求、尝试、恢复租约与快照持久化
mod source_governance;
use std::time::{Duration as StdDuration, SystemTime};
use chrono::{DateTime, Duration, Utc};
use gateway_admin::{
model::observability as admin_observability, ports::store::ObservabilityStore as _,
};
use gateway_core::diagnostics::{MAX_TRACE_SNAPSHOT_BYTES, TraceContext};
use gateway_core::engine::{
ExecutionOutcome, ExecutionStore, ModelRequestFailureObservation,
ModelRequestFinalization as CoreModelRequestFinalization, ModelRequestId,
ModelRequestTimings as CoreModelRequestTimings, NewModelRequest as CoreNewModelRequest,
};
use gateway_core::error::{
GatewayError, GatewayErrorKind, ProviderConnectionObservation, StoreErrorKind,
};
use gateway_core::metering::{CalculatedCost, CostEstimate, Usage};
use gateway_core::operation::OperationKind;
use gateway_core::policy::ClientApiKeyId;
use gateway_core::routing::{AccountRoutingSnapshot, ConfigRevision, PublicModelId};
use gateway_core::upstream::UpstreamSendState;
use gateway_store::postgres::{
ModelRequestAttemptStart, ModelRequestRepository, NewModelRequest, ObservabilityPageSize,
ObservabilityRange, OpsErrorFilter, OpsErrorQuery, PgExecutionStore, UsageRecordFilter,
UsageRecordQuery,
};
use serde_json::{Value, json};
use sqlx::PgPool;
use super::{TestDatabase, admin_observability_store, observability_repository};
#[test]
fn model_request_rejects_empty_historical_client_key_ref() {
let started_at = Utc::now();
let request = NewModelRequest {
admission_decision_ms: None,
id: "request-1".to_owned(),
client_api_key_name_snapshot: None,
client_api_key_ref: String::new(),
config_revision: 1,
protocol: "openai".to_owned(),
operation: "responses".to_owned(),
endpoint: "/v1/responses".to_owned(),
client_transport: "http_sse".to_owned(),
requested_model_id: Some("coding".to_owned()),
routing_scope: "all".to_owned(),
routing_group_refs: Vec::new(),
routing_group_names_snapshot: serde_json::json!([]),
client_ip: None,
user_agent: None,
reasoning_effort: None,
reasoning_preset: None,
request_kind: None,
subagent_kind: None,
compact: false,
continuation: Default::default(),
image_generation_requested: false,
started_at,
lease_expires_at: started_at + Duration::seconds(30),
};
assert!(request.validate().is_err());
}
#[tokio::test]
async fn request_recovery_lease_is_renewed_and_abandoned_requests_are_recovered() {
let Some(database) = TestDatabase::create("request_renewal").await else {
return;
};
let store = PgExecutionStore::new(database.pool.clone());
let mut request = accepted_request("req_long_running");
request.started_at = SystemTime::now() - StdDuration::from_secs(601);
request.deadline_at = Default::default();
store.create_model_request(request.clone()).await.unwrap();
let initial: DateTime<Utc> = sqlx::query_scalar("update model_request_leases set lease_expires_at = now() + interval '1 second' where request_id = $1 returning lease_expires_at")
.bind(request.id.as_str()).fetch_one(&database.pool).await.unwrap();
let renewal = store.maintain_request(&request.id, request.deadline_at);
let renewed: DateTime<Utc> = tokio::time::timeout(StdDuration::from_secs(2), async {
loop {
let at: DateTime<Utc> = sqlx::query_scalar(
"select lease_expires_at from model_request_leases where request_id = $1",
)
.bind(request.id.as_str())
.fetch_one(&database.pool)
.await
.unwrap();
if at > initial + Duration::seconds(500) {
break at;
}
tokio::time::sleep(StdDuration::from_millis(10)).await;
}
})
.await
.expect("live request refreshes recovery lease");
assert_eq!(
store
.recover_expired(initial.into())
.await
.unwrap()
.requests,
0
);
drop(renewal);
assert_eq!(
store
.recover_expired(renewed.into())
.await
.unwrap()
.requests,
1
);
let row = stored_row(&database.pool, request.id.as_str()).await;
assert_eq!(row["error_kind"], "process_interrupted");
database.close().await;
}
#[tokio::test]
async fn merged_model_less_first_attempt_should_match_sequential_semantics() {
let Some(database) = TestDatabase::create("execution_merged_insert").await else {
return;
};
let repository = PgExecutionStore::new(database.pool.clone());
let started_at = Utc::now();
sqlx::query(
"insert into provider_accounts (
id, provider_kind, name, email, upstream_user_id,
upstream_account_id, plan_type, authentication_kind,
provider_credentials_json, credential_revision,
has_refresh_token, access_token_expires_at, enabled, credential_state,
credential_observed_at, created_at, updated_at
) values (
'acct_merged', 'openai', 'merged', null, 'user-merged', null, null, 'oauth',
'{}'::jsonb, 1, false, $1 + interval '1 day', true, 'ready', $1, $1, $1
)",
)
.bind(started_at)
.execute(&database.pool)
.await
.expect("seed provider account");
let request = NewModelRequest {
admission_decision_ms: None,
id: "req_merged".to_owned(),
client_api_key_name_snapshot: None,
client_api_key_ref: "key_merged".to_owned(),
config_revision: 1,
protocol: "openai".to_owned(),
operation: "generate_image".to_owned(),
endpoint: "/v1/images/generations".to_owned(),
client_transport: "http_json".to_owned(),
requested_model_id: None,
routing_scope: "all".to_owned(),
routing_group_refs: Vec::new(),
routing_group_names_snapshot: serde_json::json!([]),
client_ip: None,
user_agent: None,
reasoning_effort: None,
reasoning_preset: None,
request_kind: None,
subagent_kind: None,
compact: false,
continuation: Default::default(),
image_generation_requested: false,
started_at,
lease_expires_at: started_at + Duration::seconds(30),
};
let attempt = ModelRequestAttemptStart {
account_selection_wait_ms: None,
capacity_used_slots: None,
capacity_total_slots: None,
model_request_id: "req_merged".to_owned(),
attempt_count: 1,
provider_kind: "openai".to_owned(),
account_snapshot: None,
provider_account_ref: Some("acct_merged".to_owned()),
upstream_model_id: None,
upstream_transport: "http_json".to_owned(),
http_version: None,
};
repository
.insert_model_request_with_first_attempt(request, attempt)
.await
.expect("merged insert");
let (attempt_count, send_state, provider_kind, outcome, requested_model, upstream_model): (
i32,
String,
String,
String,
Option<String>,
Option<String>,
) = sqlx::query_as(
"select attempt_count, upstream_send_state, provider_kind, outcome,
requested_model_id, upstream_model_id
from model_requests where id = 'req_merged'",
)
.fetch_one(&database.pool)
.await
.expect("load merged request");
assert_eq!(
(
attempt_count,
send_state.as_str(),
provider_kind.as_str(),
outcome.as_str(),
requested_model,
upstream_model,
),
(1, "not_sent", "openai", "running", None, None)
);
// 后续 attempt 沿用常规 CAS 递增路径;已持久化的 sent 水位不被重试重置
repository
.mark_upstream_send_state(
"req_merged",
gateway_store::postgres::UpstreamSendState::Sent,
)
.await
.expect("mark sent before retry");
let second = repository
.begin_model_request_attempt(ModelRequestAttemptStart {
account_selection_wait_ms: None,
capacity_used_slots: None,
capacity_total_slots: None,
model_request_id: "req_merged".to_owned(),
attempt_count: 2,
provider_kind: "openai".to_owned(),
account_snapshot: None,
provider_account_ref: Some("acct_merged".to_owned()),
upstream_model_id: None,
upstream_transport: "http_json".to_owned(),
http_version: None,
})
.await
.expect("second attempt");
assert_eq!(second, 2);
let send_state: String = sqlx::query_scalar(
"select upstream_send_state from model_requests where id = 'req_merged'",
)
.fetch_one(&database.pool)
.await
.expect("load send state after retry");
assert_eq!(send_state, "sent");
database.close().await;
}
#[tokio::test]
async fn model_request_persists_group_routing_snapshot_without_live_group_foreign_keys() {
let Some(database) = TestDatabase::create("execution_group_routing_history").await else {
return;
};
let repository = PgExecutionStore::new(database.pool.clone());
let started_at = Utc::now();
repository
.insert_model_request(NewModelRequest {
admission_decision_ms: None,
id: "req_group_history".to_owned(),
client_api_key_name_snapshot: None,
client_api_key_ref: "key_group_history".to_owned(),
config_revision: 7,
routing_scope: "groups".to_owned(),
routing_group_refs: vec![
"grp_00000000000000000000000000000001".to_owned(),
"grp_00000000000000000000000000000002".to_owned(),
],
routing_group_names_snapshot: serde_json::json!(["Production Pool", "Overflow Pool"]),
protocol: "openai".to_owned(),
operation: "responses".to_owned(),
endpoint: "/v1/responses".to_owned(),
client_transport: "http_sse".to_owned(),
requested_model_id: Some("coding".to_owned()),
client_ip: None,
user_agent: None,
reasoning_effort: None,
reasoning_preset: None,
request_kind: None,
subagent_kind: None,
compact: false,
continuation: Default::default(),
image_generation_requested: false,
started_at,
lease_expires_at: started_at + Duration::seconds(30),
})
.await
.expect("insert grouped request history");
let stored: (String, Vec<String>, serde_json::Value) = sqlx::query_as(
"select routing_scope, routing_group_refs, routing_group_names_snapshot
from model_request_observations where id = 'req_group_history'",
)
.fetch_one(&database.pool)
.await
.expect("load grouped request history");
assert_eq!(stored.0, "groups");
assert_eq!(
stored.1,
[
"grp_00000000000000000000000000000001",
"grp_00000000000000000000000000000002",
]
);
assert_eq!(
stored.2,
serde_json::json!(["Production Pool", "Overflow Pool"])
);
database.close().await;
}
#[tokio::test]
async fn downstream_commit_should_atomically_record_http_status_once() {
let Some(database) = TestDatabase::create("execution_commit_status").await else {
return;
};
seed_running_request(&database.pool, "req_commit_status")
.await
.expect("seed model request");
let repository = PgExecutionStore::new(database.pool.clone());
let committed = ModelRequestRepository::mark_downstream_committed(
&repository,
"req_commit_status",
Utc::now(),
Some(200),
)
.await
.expect("commit downstream");
let overwritten = repository
.record_client_status_code("req_commit_status", 500)
.await
.expect("reject status overwrite without store failure");
let (committed_at, status): (Option<chrono::DateTime<Utc>>, Option<i32>) = sqlx::query_as(
"select downstream_committed_at, client_status_code
from model_requests where id = 'req_commit_status'",
)
.fetch_one(&database.pool)
.await
.expect("load committed request");
assert!(committed);
assert!(!overwritten);
assert!(committed_at.is_some());
assert_eq!(status, Some(200));
database.close().await;
}
#[tokio::test]
async fn terminal_failure_should_accept_exactly_one_client_status_backfill() {
let Some(database) = TestDatabase::create("execution_terminal_status").await else {
return;
};
seed_running_request(&database.pool, "req_terminal_status")
.await
.expect("seed model request");
sqlx::query(
"update model_requests
set outcome = 'failed', completed_at = now()
where id = 'req_terminal_status'",
)
.execute(&database.pool)
.await
.expect("finalize model request");
let repository = PgExecutionStore::new(database.pool.clone());
let recorded = repository
.record_client_status_code("req_terminal_status", 429)
.await
.expect("record terminal client status");
let overwritten = repository
.record_client_status_code("req_terminal_status", 500)
.await
.expect("reject terminal status overwrite without store failure");
let status: Option<i32> = sqlx::query_scalar(
"select client_status_code from model_requests where id = 'req_terminal_status'",
)
.fetch_one(&database.pool)
.await
.expect("load terminal client status");
assert!(recorded);
assert!(!overwritten);
assert_eq!(status, Some(429));
database.close().await;
}
#[tokio::test]
async fn core_adapter_should_persist_calculated_cost_exactly() {
let Some(database) = TestDatabase::create("execution_calculated_cost").await else {
return;
};
seed_running_request(&database.pool, "req_calculated_cost")
.await
.expect("seed model request");
sqlx::query(
"update model_requests
set provider_kind = 'xai', provider_account_ref = 'acct_xai',
upstream_model_id = 'grok-4.5', upstream_transport = 'http_sse', attempt_count = 1
where id = 'req_calculated_cost'",
)
.execute(&database.pool)
.await
.expect("seed model request attempt");
let repository = PgExecutionStore::new(database.pool.clone());
let mut finalization = successful_core_finalization("req_calculated_cost");
finalization.downstream_committed_at = Some(std::time::SystemTime::now());
finalization.websocket_pool = Some("reuse".to_owned());
finalization.service_tier = Some("priority".to_owned());
finalization.cost = CalculatedCost::from_usd_ticks(12_345)
.expect("calculated cost")
.into_estimate();
ExecutionStore::finalize_model_request(&repository, finalization)
.await
.expect("persist calculated cost");
let persisted: (
String,
String,
String,
String,
String,
String,
Option<String>,
) = sqlx::query_as(
"select cost_source, cost_amount::text, cost_currency, upstream_transport, http_version,
websocket_pool, service_tier
from model_request_observations where id = 'req_calculated_cost'",
)
.fetch_one(&database.pool)
.await
.expect("load calculated cost");
assert_eq!(
persisted,
(
"calculated".to_owned(),
"0.0000012345".to_owned(),
"USD".to_owned(),
"websocket".to_owned(),
"HTTP/2".to_owned(),
"reuse".to_owned(),
Some("priority".to_owned()),
)
);
let observation = observability_repository(&database.pool);
let detail = observation
.usage_record_detail("req_calculated_cost")
.await
.expect("request detail");
assert_eq!(
detail.request.upstream_model_id.as_deref(),
Some("grok-4.5")
);
let page = observation
.list_usage_records(UsageRecordQuery {
status: Default::default(),
range: ObservabilityRange::new(
Utc::now() - Duration::hours(1),
Utc::now() + Duration::hours(1),
)
.unwrap(),
filter: UsageRecordFilter::default(),
current_page: 1,
page_size: ObservabilityPageSize::new(10).unwrap(),
})
.await
.expect("request list");
assert_eq!(page.items.len(), 1);
database.close().await;
}
#[tokio::test]
async fn billing_snapshot_survives_later_price_changes_and_usage_detail_reads() {
use gateway_core::metering::{
CalculatedCostAmounts, CalculatedCostBreakdown, CalculatedCostRates, CurrencyCode, Decimal,
Money,
};
let Some(database) = TestDatabase::create("billing_snapshot").await else {
return;
};
seed_running_request(&database.pool, "req_billing_snapshot")
.await
.unwrap();
sqlx::query(
"update model_requests
set provider_kind = 'xai', upstream_model_id = 'grok-4'
where id = 'req_billing_snapshot'",
)
.execute(&database.pool)
.await
.unwrap();
let money = |ticks| {
Money::new(
Decimal::from_scaled(ticks).unwrap(),
CurrencyCode::new("USD").unwrap(),
)
};
let billing = CalculatedCostBreakdown::new(
CalculatedCostAmounts::new(
money(1_000_000),
money(2_000_000),
money(3_000_000),
money(0),
money(6_000_000),
money(6_000_000),
),
CalculatedCostRates::new(
money(10_000_000_000),
money(20_000_000_000),
money(5_000_000_000),
money(0),
),
Some("default".to_owned()),
100,
)
.with_long_context_billing(true)
.with_custom_multiplier(12500)
.unwrap();
let mut finalization = successful_core_finalization("req_billing_snapshot");
finalization.cost = billing.calculated_cost().into_estimate();
ExecutionStore::finalize_model_request(
&PgExecutionStore::new(database.pool.clone()),
finalization,
)
.await
.unwrap();
let observation = admin_observability_store(&database.pool);
let original = observation
.usage_record_detail("req_billing_snapshot")
.await
.unwrap();
let Some(admin_observability::UsageBilling::Calculated(saved)) = &original.request.billing
else {
panic!("persisted billing breakdown must be restored without provider recalculation");
};
assert!(saved.long_context_billing_applied);
assert_eq!(saved.custom_multiplier_bps, 12500);
assert_eq!(saved.total_amount.amount, "0.00075".parse().unwrap());
sqlx::query("update runtime_settings set pricing_overrides_json = $1 where id = 1")
.bind(json!({"xai":{"grok-4":{"multiplierBps":90000,"bands":{}}}}))
.execute(&database.pool)
.await
.unwrap();
let after = observation
.usage_record_detail("req_billing_snapshot")
.await
.unwrap();
assert_eq!(original.request.billing, after.request.billing);
sqlx::query("update model_requests set billing_snapshot_json = billing_snapshot_json - 'longContextBillingApplied' where id = 'req_billing_snapshot'")
.execute(&database.pool).await.unwrap();
let legacy = observation
.usage_record_detail("req_billing_snapshot")
.await
.unwrap();
let Some(admin_observability::UsageBilling::Calculated(legacy)) = legacy.request.billing else {
panic!("legacy billing snapshot must remain readable");
};
assert!(!legacy.long_context_billing_applied);
assert_eq!(legacy.total_amount, saved.total_amount);
database.close().await;
}
#[tokio::test]
async fn core_adapter_should_persist_image_result_and_new_websocket_pool() {
let Some(database) = TestDatabase::create("execution_image_usage").await else {
return;
};
seed_running_request(&database.pool, "req_image_usage")
.await
.expect("seed image request");
sqlx::query(
"update model_requests
set image_generation_requested = true, provider_kind = 'openai',
provider_account_ref = 'acct_openai', upstream_model_id = 'gpt-image',
upstream_transport = 'websocket', attempt_count = 1
where id = 'req_image_usage'",
)
.execute(&database.pool)
.await
.expect("seed image attempt");
let repository = PgExecutionStore::new(database.pool.clone());
let mut usage = Usage::new();
usage.image_input_tokens = Some(31);
usage.image_output_tokens = Some(9);
let mut finalization = successful_core_finalization("req_image_usage");
finalization.usage = usage;
finalization.image_generation_succeeded = Some(true);
finalization.websocket_pool = Some("new".to_owned());
ExecutionStore::finalize_model_request(&repository, finalization)
.await
.expect("persist image usage");
let persisted: (bool, Option<i64>, Option<i64>, Option<bool>, Option<String>) = sqlx::query_as(
"select image_generation_requested, image_input_tokens, image_output_tokens,
image_generation_succeeded, websocket_pool
from model_request_observations where id = 'req_image_usage'",
)
.fetch_one(&database.pool)
.await
.expect("load image usage");
assert_eq!(
persisted,
(true, Some(31), Some(9), Some(true), Some("new".to_owned()))
);
database.close().await;
}
#[tokio::test]
async fn expired_image_request_should_be_recovered_as_failed() {
let Some(database) = TestDatabase::create("execution_expired_image").await else {
return;
};
seed_running_request(&database.pool, "req_expired_image")
.await
.expect("seed image request");
sqlx::query(
"update model_requests
set image_generation_requested = true,
started_at = now() - interval '2 seconds',
recovery_lease_expires_at = now() - interval '1 second'
where id = 'req_expired_image'",
)
.execute(&database.pool)
.await
.expect("expire image request");
let repository = PgExecutionStore::new(database.pool.clone());
repository
.recover_expired_model_requests(Utc::now())
.await
.expect("recover expired image request");
let persisted: (String, Option<bool>) = sqlx::query_as(
"select outcome, image_generation_succeeded
from model_requests where id = 'req_expired_image'",
)
.fetch_one(&database.pool)
.await
.expect("load recovered image request");
assert_eq!(persisted, ("incomplete".to_owned(), Some(false)));
database.close().await;
}
fn successful_core_finalization(id: &str) -> CoreModelRequestFinalization {
CoreModelRequestFinalization {
diagnostic_trace_json: None,
request_id: ModelRequestId::new(id).expect("request id"),
outcome: ExecutionOutcome::Succeeded,
send_state: UpstreamSendState::Sent,
attempt_count: 1,
downstream_committed_at: None,
client_status_code: Some(200),
client_response_id: None,
upstream_status_code: Some(200),
upstream_request_id: None,
upstream_response_id: None,
upstream_transport: Some("websocket".to_owned()),
http_version: Some("HTTP/2".to_owned()),
websocket_pool: None,
service_tier: None,
provider_metadata_json: None,
error: None,
provider_error_code: None,
error_details: None,
failure_observation: Default::default(),
retry_after_ms: None,
usage: Usage::new(),
image_generation_succeeded: None,
cost: CostEstimate::unavailable(),
timings: CoreModelRequestTimings::default(),
completed_at: std::time::SystemTime::now(),
}
}
#[tokio::test]
async fn clock_rollback_preserves_terminal_outcomes_and_errors_after_recovery() {
let Some(database) = TestDatabase::create("execution_clock_rollback").await else {
return;
};
let store = PgExecutionStore::new(database.pool.clone());
for (index, outcome) in [
ExecutionOutcome::Succeeded,
ExecutionOutcome::Failed,
ExecutionOutcome::Cancelled,
ExecutionOutcome::Incomplete,
]
.into_iter()
.enumerate()
{
let id = format!("req_clock_rollback_{index}");
let request = accepted_request(&id);
store
.create_model_request(request.clone())
.await
.expect("create request before clock rollback");
store
.begin_model_request_attempt(ModelRequestAttemptStart {
account_selection_wait_ms: None,
capacity_used_slots: None,
capacity_total_slots: None,
model_request_id: id.clone(),
attempt_count: 1,
provider_kind: "openai".to_owned(),
account_snapshot: None,
provider_account_ref: Some("acct_clock_rollback".to_owned()),
upstream_model_id: Some("coding".to_owned()),
upstream_transport: "websocket".to_owned(),
http_version: None,
})
.await
.expect("record real attempt before clock rollback");
let mut finalization = successful_core_finalization(&id);
finalization.outcome = outcome;
finalization.timings.latency_ms = Some(125);
finalization.completed_at = request.started_at - StdDuration::from_millis(400);
if outcome != ExecutionOutcome::Succeeded {
finalization.error = Some(GatewayError::new(
GatewayErrorKind::RateLimited,
"synthetic upstream quota error",
));
finalization.provider_error_code = Some("usage_limit_reached".to_owned());
finalization.upstream_status_code = Some(429);
}
ExecutionStore::finalize_model_request(&store, finalization)
.await
.expect("finalize despite wall clock rollback");
let terminal = stored_row(&database.pool, &id).await;
assert_eq!(terminal["completed_at"], terminal["started_at"]);
assert_eq!(terminal["latency_ms"], 125);
assert_eq!(
terminal["outcome"],
["succeeded", "failed", "cancelled", "incomplete"][index]
);
if outcome != ExecutionOutcome::Succeeded {
assert_eq!(terminal["error_kind"], "rate_limited");
assert_eq!(terminal["provider_error_code"], "usage_limit_reached");
assert_eq!(terminal["error_message"], "synthetic upstream quota error");
}
assert_eq!(
store
.recover_expired(request.deadline_at.at().unwrap())
.await
.expect("recovery leaves finalized request intact")
.requests,
0
);
assert_eq!(stored_row(&database.pool, &id).await, terminal);
}
database.close().await;
}
#[tokio::test]
async fn zero_attempt_finalization_preserves_real_error_after_clock_rollback() {
let Some(database) = TestDatabase::create("zero_attempt_clock_rollback").await else {
return;
};
let store = PgExecutionStore::new(database.pool.clone());
let request = accepted_request("req_zero_attempt_clock_rollback");
store
.create_model_request(request.clone())
.await
.expect("create zero attempt request");
let mut finalization = early_failure(&request);
finalization.completed_at = request.started_at - StdDuration::from_millis(400);
ExecutionStore::finalize_model_request(&store, finalization)
.await
.expect("finalize zero attempt after clock rollback");
assert_eq!(
store
.recover_expired(request.deadline_at.at().unwrap())
.await
.expect("recovery preserves real early failure")
.requests,
0
);
let terminal = stored_row(&database.pool, request.id.as_str()).await;
assert_eq!(terminal["outcome"], "failed");
assert_eq!(terminal["error_kind"], "no_available_provider");
assert_eq!(terminal["attempt_count"], 0);
assert_eq!(terminal["completed_at"], terminal["started_at"]);
database.close().await;
}
#[tokio::test]
async fn core_adapter_persists_opaque_response_ids_as_bytes() {
let Some(database) = TestDatabase::create("execution_opaque_response_id").await else {
return;
};
seed_running_request(&database.pool, "req_opaque_response_id")
.await
.expect("seed model request");
let store = PgExecutionStore::new(database.pool.clone());
let response_id = format!("resp_{}\0opaque", "x".repeat(4_096));
let mut finalization = successful_core_finalization("req_opaque_response_id");
finalization.client_response_id = Some(response_id.clone());
finalization.upstream_response_id = Some(response_id.clone());
ExecutionStore::finalize_model_request(&store, finalization)
.await
.expect("persist opaque response IDs");
let persisted: (Option<Vec<u8>>, Option<Vec<u8>>) = sqlx::query_as(
"select client_response_id, upstream_response_id
from model_requests where id = 'req_opaque_response_id'",
)
.fetch_one(&database.pool)
.await
.expect("load opaque response IDs");
assert_eq!(persisted.0.as_deref(), Some(response_id.as_bytes()));
assert_eq!(persisted.1.as_deref(), Some(response_id.as_bytes()));
database.close().await;
}
#[tokio::test]
async fn core_adapter_persists_native_causes_and_verbatim_upstream_details() {
let Some(database) = TestDatabase::create("execution_error_details").await else {
return;
};
seed_running_request(&database.pool, "req_raw_upstream_error")
.await
.expect("seed model request");
let store = PgExecutionStore::new(database.pool.clone());
let raw = r#"{"error":{"message":"raw upstream body","opaque":"\u0000"}}"#;
let provider_error = gateway_core::error::ProviderError::new(
gateway_core::error::ProviderErrorKind::Unavailable,
UpstreamSendState::Sent,
)
.with_source(std::io::Error::other("original local cause"))
.with_raw_upstream_error(gateway_core::error::RawUpstreamError::new(raw));
let mut finalization = successful_core_finalization("req_raw_upstream_error");
finalization.outcome = ExecutionOutcome::Failed;
finalization.client_status_code = Some(502);
finalization.upstream_status_code = Some(500);
finalization.error = Some(GatewayError::new(
GatewayErrorKind::UpstreamUnavailable,
"upstream service is unavailable",
));
finalization.error_details = provider_error.error_details();
ExecutionStore::finalize_model_request(&store, finalization)
.await
.expect("persist restricted error details");
let persisted: Option<String> = sqlx::query_scalar(
"select error_details from model_requests where id = 'req_raw_upstream_error'",
)
.fetch_one(&database.pool)
.await
.expect("load restricted error details");
let persisted: Value = serde_json::from_str(persisted.as_deref().unwrap()).unwrap();
assert_eq!(persisted["upstream"], raw);
assert_eq!(
persisted["causes"]["messages"],
json!(["original local cause"])
);
assert_eq!(persisted["causes"]["truncated"], false);
database.close().await;
}
#[tokio::test]
async fn core_adapter_should_persist_only_object_provider_observation() {
let Some(database) = TestDatabase::create("execution_provider_observation").await else {
return;
};
seed_running_request(&database.pool, "req_provider_observation")
.await
.expect("seed model request");
let store = PgExecutionStore::new(database.pool.clone());
let mut finalization = successful_core_finalization("req_provider_observation");
finalization.provider_metadata_json = Some("{\"effectiveModel\":\"gpt-test\"}".to_owned());
ExecutionStore::finalize_model_request(&store, finalization)
.await
.expect("persist provider observation");
let persisted: serde_json::Value = sqlx::query_scalar(
"select provider_observation_json from model_requests where id = 'req_provider_observation'",
)
.fetch_one(&database.pool)
.await
.expect("load provider observation");
assert_eq!(persisted["effectiveModel"], "gpt-test");
database.close().await;
}
#[tokio::test]
async fn core_adapter_should_reject_non_object_provider_observation() {
let Some(database) = TestDatabase::create("execution_invalid_provider_observation").await
else {
return;
};
seed_running_request(&database.pool, "req_invalid_provider_observation")
.await
.expect("seed model request");
let store = PgExecutionStore::new(database.pool.clone());
let mut finalization = successful_core_finalization("req_invalid_provider_observation");
finalization.provider_metadata_json = Some("[]".to_owned());
assert!(
ExecutionStore::finalize_model_request(&store, finalization)
.await
.is_err()
);
database.close().await;
}
#[tokio::test]
async fn core_adapter_persists_continuation_connection_failure_observation() {
let Some(database) = TestDatabase::create("execution_continuation_observation").await else {
return;
};
seed_running_request(&database.pool, "req_continuation_observation")
.await
.expect("seed model request");
let store = PgExecutionStore::new(database.pool.clone());
let mut finalization = successful_core_finalization("req_continuation_observation");
finalization.outcome = ExecutionOutcome::Failed;
finalization.client_status_code = Some(400);
finalization.error = Some(GatewayError::new(
GatewayErrorKind::InvalidRequest,
"conversation continuation must be rebuilt",
));
finalization.failure_observation = ModelRequestFailureObservation {
continuation_unavailable_reason: Some("reused_connection_lost".to_owned()),
upstream_connection: Some(ProviderConnectionObservation::new(
"connection-observed",
"tcp_reset",
12_000,
4_000,
)),
};
ExecutionStore::finalize_model_request(&store, finalization)
.await
.expect("persist continuation failure observation");
type PersistedContinuationObservation = (
Option<String>,
Option<String>,
Option<String>,
Option<String>,
Option<i64>,
Option<i64>,
);
let persisted: PersistedContinuationObservation = sqlx::query_as(
"select error_kind, continuation_unavailable_reason,
upstream_connection_id, upstream_connection_exit_reason,
upstream_connection_age_ms, upstream_connection_idle_ms
from model_request_observations where id = 'req_continuation_observation'",
)
.fetch_one(&database.pool)
.await
.expect("load continuation failure observation");
assert_eq!(
persisted.0.as_deref(),
Some("continuation_recovery_required")
);
assert_eq!(persisted.1.as_deref(), Some("reused_connection_lost"));
assert_eq!(persisted.2.as_deref(), Some("connection-observed"));
assert_eq!(persisted.3.as_deref(), Some("tcp_reset"));
assert_eq!(persisted.4, Some(12_000));
assert_eq!(persisted.5, Some(4_000));
database.close().await;
}
#[tokio::test]
async fn successful_new_chain_should_mark_recent_continuation_failure_recovered() {
let Some(database) = TestDatabase::create("execution_continuation_recovered").await else {
return;
};
let affinity_hash = "a".repeat(64);
let failure_completed_at = Utc::now() - Duration::seconds(8);
seed_continuation_failure(
&database.pool,
"req_continuation_failed",
&affinity_hash,
failure_completed_at,
)
.await;
seed_recovery_request(
&database.pool,
"req_continuation_recovery",
&affinity_hash,
failure_completed_at + Duration::seconds(4),
)
.await;
let store = PgExecutionStore::new(database.pool.clone());
ExecutionStore::finalize_model_request(
&store,
successful_core_finalization("req_continuation_recovery"),
)
.await
.expect("finalize successful recovery request");
type PersistedRecoveryObservation = (
Option<String>,
Option<chrono::DateTime<Utc>>,
i32,
Option<i64>,
Option<i64>,
);
let recovered: PersistedRecoveryObservation = sqlx::query_as(
"select recovery_request_id, recovered_at, recovery_attempt_count,
recovery_retry_delay_ms, recovery_total_latency_ms
from model_request_observations where id = 'req_continuation_failed'",
)
.fetch_one(&database.pool)
.await
.expect("load recovered continuation failure");
assert_eq!(recovered.0.as_deref(), Some("req_continuation_recovery"));
assert!(recovered.1.is_some());
assert_eq!(recovered.2, 1);
assert_eq!(recovered.3, Some(4_000));
assert!(recovered.4.is_some_and(|latency| latency >= 4_000));
database.close().await;
}
#[tokio::test]
async fn successful_http_downgrade_should_mark_pending_websocket_failures_recovered() {
let Some(database) = TestDatabase::create("execution_transport_downgrade_recovered").await
else {
return;
};
let affinity_hash = "d".repeat(64);
let first_failure_at = Utc::now() - Duration::seconds(8);
let second_failure_at = first_failure_at + Duration::seconds(1);
seed_websocket_transport_failure(
&database.pool,
"req_websocket_failed_first",
&affinity_hash,
first_failure_at,
)
.await;
seed_websocket_transport_failure(
&database.pool,
"req_websocket_failed_second",
&affinity_hash,
second_failure_at,
)
.await;
seed_websocket_transport_failure(
&database.pool,
"req_websocket_failed_after_delivery",
&affinity_hash,
second_failure_at,
)
.await;
sqlx::query(
"update model_requests
set downstream_committed_at = completed_at
where id = 'req_websocket_failed_after_delivery'",
)
.execute(&database.pool)
.await
.expect("mark control failure delivered downstream");
let recovery_started_at = first_failure_at + Duration::seconds(4);
seed_transport_recovery_request(
&database.pool,
"req_http_fallback_succeeded",
&affinity_hash,
recovery_started_at,
)
.await;
let store = PgExecutionStore::new(database.pool.clone());
let mut recovery = successful_core_finalization("req_http_fallback_succeeded");
recovery.upstream_transport = Some("http_sse".to_owned());
recovery.downstream_committed_at = Some(std::time::SystemTime::now());
ExecutionStore::finalize_model_request(&store, recovery)
.await
.expect("finalize successful HTTP fallback");
type PersistedRecovery = (
String,
Option<String>,
Option<chrono::DateTime<Utc>>,
i32,
Option<i64>,
Option<i64>,
);
let recovered: Vec<PersistedRecovery> = sqlx::query_as(
"select id, recovery_request_id, recovered_at, recovery_attempt_count,
recovery_retry_delay_ms, recovery_total_latency_ms
from model_request_observations
where id like 'req_websocket_failed_%'
order by id",
)
.fetch_all(&database.pool)
.await
.expect("load WebSocket recovery observations");
assert_eq!(recovered.len(), 3);
let delivered = &recovered[0];
assert_eq!(delivered.0, "req_websocket_failed_after_delivery");
assert_eq!((&delivered.1, delivered.2, delivered.3), (&None, None, 0));
for (row, expected_delay_ms) in recovered[1..].iter().zip([4_000, 3_000]) {
assert_eq!(row.1.as_deref(), Some("req_http_fallback_succeeded"));
assert!(row.2.is_some());
assert_eq!(row.3, 1);
assert_eq!(row.4, Some(expected_delay_ms));
assert!(row.5.is_some_and(|latency| latency >= expected_delay_ms));
}
let repository = super::observability_repository(&database.pool);
let failed_detail = repository
.usage_record_detail("req_websocket_failed_first")
.await
.unwrap();
assert_eq!(
failed_detail.related_requests[0]["requestId"],
"req_http_fallback_succeeded"
);
assert_eq!(
failed_detail.related_requests[0]["relation"],
"recovered_by"
);
let recovered_detail = repository
.usage_record_detail("req_http_fallback_succeeded")
.await
.unwrap();
assert_eq!(recovered_detail.related_requests.len(), 2);
assert!(
recovered_detail
.related_requests
.iter()
.all(|related| related["relation"] == "recovers")
);
database.close().await;
}
#[tokio::test]
async fn websocket_success_or_late_http_success_should_not_hide_transport_failure() {
let Some(database) = TestDatabase::create("execution_transport_downgrade_unresolved").await
else {
return;
};
let failure_at = Utc::now() - Duration::seconds(60);
let store = PgExecutionStore::new(database.pool.clone());
let websocket_affinity = "e".repeat(64);
seed_websocket_transport_failure(
&database.pool,
"req_websocket_control_failure",
&websocket_affinity,
failure_at,
)
.await;
seed_transport_recovery_request(
&database.pool,
"req_websocket_control_success",
&websocket_affinity,
failure_at + Duration::seconds(4),
)
.await;
ExecutionStore::finalize_model_request(
&store,
successful_core_finalization("req_websocket_control_success"),
)
.await
.expect("finalize successful WebSocket control request");
let late_affinity = "f".repeat(64);
seed_websocket_transport_failure(
&database.pool,
"req_websocket_late_failure",
&late_affinity,
failure_at,
)
.await;
seed_transport_recovery_request(
&database.pool,
"req_http_late_success",
&late_affinity,
failure_at + Duration::seconds(31),
)
.await;
let mut late_recovery = successful_core_finalization("req_http_late_success");
late_recovery.upstream_transport = Some("http_sse".to_owned());
ExecutionStore::finalize_model_request(&store, late_recovery)
.await
.expect("finalize late HTTP request");
type UnresolvedTransportFailure = (String, Option<String>, Option<chrono::DateTime<Utc>>, i32);
let unresolved: Vec<UnresolvedTransportFailure> = sqlx::query_as(
"select id, recovery_request_id, recovered_at, recovery_attempt_count
from model_requests
where id in ('req_websocket_control_failure', 'req_websocket_late_failure')
order by id",
)
.fetch_all(&database.pool)
.await
.expect("load unresolved transport failures");
assert_eq!(unresolved.len(), 2);
assert!(
unresolved
.iter()
.all(|row| row.1.is_none() && row.2.is_none() && row.3 == 0)
);
database.close().await;
}
#[tokio::test]
async fn failed_or_late_new_chain_should_leave_continuation_failure_unresolved() {
let Some(database) = TestDatabase::create("execution_continuation_unresolved").await else {
return;
};
let affinity_hash = "c".repeat(64);
let failure_completed_at = Utc::now() - Duration::seconds(60);
seed_continuation_failure(
&database.pool,
"req_continuation_unresolved",
&affinity_hash,
failure_completed_at,
)
.await;
seed_recovery_request(
&database.pool,
"req_continuation_retry_failed",
&affinity_hash,
failure_completed_at + Duration::seconds(3),
)
.await;
let store = PgExecutionStore::new(database.pool.clone());
let mut failed = successful_core_finalization("req_continuation_retry_failed");
failed.outcome = ExecutionOutcome::Failed;
failed.client_status_code = Some(502);
failed.upstream_status_code = Some(500);
failed.error = Some(GatewayError::new(
GatewayErrorKind::UpstreamUnavailable,
"recovery request failed",
));
ExecutionStore::finalize_model_request(&store, failed)
.await
.expect("finalize failed recovery request");
let after_failed: (Option<String>, Option<chrono::DateTime<Utc>>, i32) = sqlx::query_as(
"select recovery_request_id, recovered_at, recovery_attempt_count
from model_requests where id = 'req_continuation_unresolved'",
)
.fetch_one(&database.pool)
.await
.expect("load unresolved continuation failure");
assert_eq!(after_failed, (None, None, 1));
seed_recovery_request(
&database.pool,
"req_continuation_retry_late",
&affinity_hash,
failure_completed_at + Duration::seconds(31),
)
.await;
ExecutionStore::finalize_model_request(
&store,
successful_core_finalization("req_continuation_retry_late"),
)
.await
.expect("finalize late recovery request");
let after_late: (Option<String>, Option<chrono::DateTime<Utc>>, i32) = sqlx::query_as(
"select recovery_request_id, recovered_at, recovery_attempt_count
from model_requests where id = 'req_continuation_unresolved'",
)
.fetch_one(&database.pool)
.await
.expect("load late unresolved continuation failure");
assert_eq!(after_late, (None, None, 1));
database.close().await;
}
async fn seed_continuation_failure(
pool: &sqlx::PgPool,
id: &str,
affinity_hash: &str,
completed_at: chrono::DateTime<Utc>,
) {
seed_running_request(pool, id)
.await
.expect("seed continuation failure request");
sqlx::query(
"update model_requests
set client_api_key_ref = 'key_continuation',
client_transport = 'websocket',
continuation_affinity_hash = $2,
continuation_requested = true,
outcome = 'failed',
error_kind = 'continuation_recovery_required',
started_at = $4 - interval '1 second',
recovery_lease_expires_at = $4,
completed_at = $4,
request_observation_json = jsonb_strip_nulls(request_observation_json || jsonb_build_object(
'continuation', coalesce(request_observation_json #> '{continuation}', '{}'::jsonb) || jsonb_build_object(
'previousResponseIdHash', $3::text,
'unavailableReason', 'reused_connection_lost')))
where id = $1",
)
.bind(id)
.bind(affinity_hash)
.bind("b".repeat(64))
.bind(completed_at)
.execute(pool)
.await
.expect("finalize seeded continuation failure");
}
async fn seed_recovery_request(
pool: &sqlx::PgPool,
id: &str,
affinity_hash: &str,
started_at: chrono::DateTime<Utc>,
) {
seed_running_request(pool, id)
.await
.expect("seed continuation recovery request");
sqlx::query(
"update model_requests
set client_api_key_ref = 'key_continuation', client_transport = 'websocket',
continuation_affinity_hash = $2, continuation_requested = false,
started_at = $3, recovery_lease_expires_at = $3 + interval '1 minute'
where id = $1",
)
.bind(id)
.bind(affinity_hash)
.bind(started_at)
.execute(pool)
.await
.expect("prepare seeded recovery request");
}
async fn seed_websocket_transport_failure(
pool: &sqlx::PgPool,
id: &str,
affinity_hash: &str,
completed_at: chrono::DateTime<Utc>,
) {
seed_running_request(pool, id)
.await
.expect("seed WebSocket transport failure request");
sqlx::query(
"update model_requests
set client_api_key_ref = 'key_transport_recovery',
client_transport = 'http_sse',
continuation_affinity_hash = $2,
attempt_count = 1,
upstream_send_state = 'ambiguous',
upstream_transport = 'websocket',
outcome = 'failed',
error_kind = 'upstream_unavailable',
started_at = $3 - interval '1 second',
recovery_lease_expires_at = $3,
completed_at = $3,
request_observation_json = jsonb_strip_nulls(request_observation_json || jsonb_build_object(
'error', coalesce(request_observation_json #> '{error}', '{}'::jsonb) || jsonb_build_object(
'providerErrorCode', 'websocket_close_1000')))
where id = $1",
)
.bind(id)
.bind(affinity_hash)
.bind(completed_at)
.execute(pool)
.await
.expect("finalize seeded WebSocket transport failure");
}
async fn seed_transport_recovery_request(
pool: &sqlx::PgPool,
id: &str,
affinity_hash: &str,
started_at: chrono::DateTime<Utc>,
) {
seed_running_request(pool, id)
.await
.expect("seed transport recovery request");
sqlx::query(
"update model_requests
set client_api_key_ref = 'key_transport_recovery',
client_transport = 'http_sse',
continuation_affinity_hash = $2,
started_at = $3, recovery_lease_expires_at = $3 + interval '1 minute'
where id = $1",
)
.bind(id)
.bind(affinity_hash)
.bind(started_at)
.execute(pool)
.await
.expect("prepare seeded transport recovery request");
}
async fn seed_running_request(pool: &sqlx::PgPool, id: &str) -> Result<(), sqlx::Error> {
sqlx::query(
"insert into model_requests (
id, client_api_key_ref, operation, client_transport, requested_model_id, provider_kind, provider_account_ref, cost_source, started_at, recovery_lease_expires_at, request_observation_json
) values (
$1, 'key_status', 'generate', 'http_json', 'status-model', 'openai', 'acct_status', 'unavailable', now(), now() + interval '1 minute',
jsonb_strip_nulls(jsonb_build_object(
'request', jsonb_build_object(
'configRevision', 1,
'protocol', 'openai_responses',
'endpoint', '/v1/responses',
'compact', false),
'routing', jsonb_build_object(
'scope', 'all',
'groupRefs', '{}'::text[],
'groupNamesSnapshot', '[]'::jsonb)))
)",
)
.bind(id)
.execute(pool)
.await?;
Ok(())
}
#[tokio::test]
async fn diagnostic_trace_pressure_preserves_terminal_outcomes_and_usage() {
let Some(database) = TestDatabase::create("diagnostic_trace").await else {
return;
};
let store = PgExecutionStore::new(database.pool.clone());
let repository = super::observability_repository(&database.pool);
for (id, outcome, expected_outcome) in [
(
"req_diagnostic_success",
ExecutionOutcome::Succeeded,
"succeeded",
),
("req_diagnostic_failed", ExecutionOutcome::Failed, "failed"),
] {
seed_running_request(&database.pool, id).await.unwrap();
let mut finalization = successful_core_finalization(id);
finalization.outcome = outcome;
finalization.usage.input_tokens = Some(100_000);
finalization.usage.output_tokens = Some(2000);
finalization.usage.total_tokens = Some(102_000);
finalization.cost = CalculatedCost::from_usd_ticks(10_000_000_000)
.unwrap()
.into_estimate();
finalization.timings.first_token_ms = Some(120);
finalization.timings.latency_ms = Some(42_000);
let trace = TraceContext::new(id);
trace.record("request.routed", json!({"scope": "all"}));
for stage in ["provider.request.input", "provider.request.outbound"] {
trace.record_evidence(stage, json!({"hashes": "a".repeat(6000)}));
}
for index in 0..500 {
trace.record(
"stream.sample",
json!({"index": index, "detail": "x".repeat(2000)}),
);
}
let error_message = if outcome == ExecutionOutcome::Failed {
finalization.client_status_code = Some(502);
let message = "OpenAI WebSocket receive idle timeout after 300s";
let provider_error = gateway_core::error::ProviderError::new(
gateway_core::error::ProviderErrorKind::Timeout,
UpstreamSendState::Ambiguous,
)
.with_diagnostic(
gateway_core::error::ProviderDiagnostic::new(message)
.with_classification("receive", "receive_idle_timeout"),
);
trace.record_provider_failure(&provider_error);
finalization.error = Some(GatewayError::from_provider(&provider_error));
Some(message)
} else {
None
};
trace.record("request.finished", json!({"outcome": expected_outcome}));
let snapshot = trace.snapshot().unwrap();
let encoded = snapshot.to_string();
assert!(encoded.len() > 64 * 1024);
assert!(encoded.len() <= MAX_TRACE_SNAPSHOT_BYTES);
assert!(snapshot["droppedEvents"].as_u64().unwrap() > 0);
finalization.diagnostic_trace_json = Some(encoded);
ExecutionStore::finalize_model_request(&store, finalization)
.await
.expect("trace pressure must not discard the terminal result");
let persisted: Value = sqlx::query_scalar(
"select jsonb_build_object(
'outcome', outcome, 'input', input_tokens, 'output', output_tokens,
'cost', cost_amount::text, 'firstToken', first_token_ms,
'latency', latency_ms, 'error', error_message)
from model_request_observations where id = $1",
)
.bind(id)
.fetch_one(&database.pool)
.await
.unwrap();
assert_eq!(
persisted,
json!({
"outcome": expected_outcome, "input": 100_000, "output": 2000,
"cost": "1.0000000000", "firstToken": 120, "latency": 42_000,
"error": error_message,
})
);
let detail = repository.usage_record_detail(id).await.unwrap();
assert_eq!(detail.trace, Some(snapshot));
assert!(detail.related_requests.is_empty());
}
assert_eq!(
store
.recover_expired_model_requests(Utc::now() + Duration::minutes(20))
.await
.unwrap()
.requests,
0,
);
database.close().await;
}
// 只构造已接纳的入口事实;未选择账号、未出站,也没有上游用量
pub(super) fn accepted_request(id: &str) -> CoreNewModelRequest {
let started_at = SystemTime::from(
DateTime::from_timestamp_micros(Utc::now().timestamp_micros())
.expect("PostgreSQL timestamp precision"),
);
CoreNewModelRequest {
id: ModelRequestId::new(id).expect("request id"),
client_api_key_name_snapshot: None,
client_api_key_ref: ClientApiKeyId::new("key_zero_attempt").expect("client key ref"),
config_revision: ConfigRevision::new(1).expect("revision"),
routing: AccountRoutingSnapshot::all(),
protocol: "openai".to_owned(),
operation: OperationKind::Generate,
endpoint: "/v1/responses".to_owned(),
client_transport: "http_sse".to_owned(),
requested_model: Some(PublicModelId::new("coding").expect("public model")),
client_ip: None,
user_agent: None,
reasoning_effort: None,
reasoning_preset: None,
request_kind: None,
subagent_kind: None,
compact: false,
continuation: Default::default(),
image_generation_requested: false,
admission_decision_ms: Some(2),
started_at,
deadline_at: (started_at + StdDuration::from_secs(30)).into(),
}
}
pub(super) fn early_failure(request: &CoreNewModelRequest) -> CoreModelRequestFinalization {
let trace = TraceContext::new(request.id.as_str());
trace.record("request.started", json!({"operation": "generate"}));
// trace 的 index 是预备阶段关联,不证明已经拿到账号、建流或实际发送
let preparation = trace.attempt(1);
preparation.record(
"attempt.started",
json!({"provider": "openai", "model": "coding", "pinnedAccount": null}),
);
preparation.record(
"attempt.failed",
json!({"kind": "no_eligible_account", "sendState": "NotSent"}),
);
trace.record(
"request.finished",
json!({"outcome": "failed", "errorKind": "no_available_provider", "attemptCount": 0}),
);
CoreModelRequestFinalization {
request_id: request.id.clone(),
outcome: ExecutionOutcome::Failed,
send_state: UpstreamSendState::NotSent,
attempt_count: 0,
downstream_committed_at: None,
client_status_code: Some(503),
client_response_id: None,
upstream_status_code: None,
upstream_request_id: None,
upstream_response_id: None,
upstream_transport: None,
http_version: None,
websocket_pool: None,
service_tier: None,
provider_metadata_json: None,
diagnostic_trace_json: trace.snapshot().map(|value| value.to_string()),
error: Some(GatewayError::new(
GatewayErrorKind::NoAvailableProvider,
"no available provider",
)),
provider_error_code: None,
error_details: None,
failure_observation: Default::default(),
retry_after_ms: None,
usage: Usage::new(),
image_generation_succeeded: None,
cost: CostEstimate::unavailable(),
timings: CoreModelRequestTimings {
latency_ms: Some(1_000),
..Default::default()
},
completed_at: request.started_at + StdDuration::from_secs(1),
}
}
async fn stored_row(pool: &PgPool, request_id: &str) -> Value {
sqlx::query_scalar("select to_jsonb(mr) from model_request_observations mr where id = $1")
.bind(request_id)
.fetch_one(pool)
.await
.expect("load complete synthetic request row")
}
fn zero_attempt_range(request: &CoreNewModelRequest) -> ObservabilityRange {
ObservabilityRange::new(
DateTime::from(request.started_at - StdDuration::from_secs(60)),
DateTime::from(request.deadline_at.at().unwrap() + StdDuration::from_secs(60)),
)
.expect("observation range")
}
#[tokio::test]
async fn zero_attempt_failure_is_queryable_without_fabricating_upstream_facts() {
let Some(database) = TestDatabase::create("zero_attempt_queries").await else {
return;
};
let store = PgExecutionStore::new(database.pool.clone());
let request = accepted_request("req_zero_attempt_queries");
store
.create_model_request(request.clone())
.await
.expect("create accepted request without attempt");
let finalization = early_failure(&request);
let trace: Value = serde_json::from_str(
finalization
.diagnostic_trace_json
.as_deref()
.expect("trace snapshot"),
)
.expect("trace JSON");
ExecutionStore::finalize_model_request(&store, finalization)
.await
.expect("finalize zero-attempt failure with trace");
let repository = observability_repository(&database.pool);
let detail = repository
.usage_record_detail(request.id.as_str())
.await
.expect("load failed request detail");
assert_eq!(detail.request.outcome.as_str(), "failed");
assert_eq!(detail.request.attempt_count, 0);
assert_eq!(detail.request.upstream_send_state, "not_sent");
assert_eq!(detail.request.client_api_key_ref, "key_zero_attempt");
assert_eq!(detail.request.requested_model_id.as_deref(), Some("coding"));
assert_eq!(detail.request.admission_decision_ms, Some(2));
assert_eq!(detail.request.latency_ms, Some(1_000));
assert_eq!(detail.request.client_status_code, Some(503));
assert_eq!(
detail.request.error_kind.as_deref(),
Some("no_available_provider")
);
assert_eq!(
detail.request.error_message.as_deref(),
Some("no available provider")
);
assert!(detail.attempts.is_empty());
assert!(detail.related_requests.is_empty());
assert_eq!(detail.trace, Some(trace.clone()));
let row = stored_row(&database.pool, request.id.as_str()).await;
for field in [
"provider_kind",
"provider_account_ref",
"provider_account_name_snapshot",
"provider_account_email_snapshot",
"provider_account_authentication_kind_snapshot",
"upstream_model_id",
"upstream_transport",
"http_version",
"upstream_status_code",
"upstream_request_id",
"upstream_response_id",
"client_response_id",
"downstream_committed_at",
"service_tier",
"provider_observation_json",
"error_details",
"input_tokens",
"output_tokens",
"total_tokens",
"cost_amount",
"cost_currency",
"account_selection_wait_ms",
"capacity_used_slots",
"capacity_total_slots",
] {
assert_eq!(
row.get(field),
Some(&Value::Null),
"{field} must remain absent"
);
}
assert_eq!(row["cost_source"], "unavailable");
let events = trace["events"].as_array().expect("events");
assert!(events.iter().all(|event| event["exchangeId"].is_null()));
let prepared_attempts: Vec<_> = events
.iter()
.filter(|event| event["attemptIndex"] == 1)
.map(|event| event["stage"].as_str().expect("stage"))
.collect();
assert_eq!(prepared_attempts, ["attempt.started", "attempt.failed"]);
let query = OpsErrorQuery {
range: zero_attempt_range(&request),
filter: OpsErrorFilter {
request_id: Some(request.id.as_str().to_owned()),
..Default::default()
},
current_page: 1,
page_size: ObservabilityPageSize::new(10).expect("page size"),
};
let errors = repository
.list_ops_errors(query.clone())
.await
.expect("ops errors");
assert_eq!(errors.total, 1);
assert_eq!(errors.items.len(), 1);
let error = &errors.items[0];
assert_eq!(error.source, "model_request");
assert_eq!(error.request_id.as_deref(), Some(request.id.as_str()));
assert_eq!(error.attempt_index, None);
assert_eq!(error.failure_kind, "no_available_provider");
assert_eq!(error.message, "no available provider");
assert_eq!(error.upstream_send_state.as_deref(), Some("not_sent"));
assert_eq!(error.provider_kind, None);
assert_eq!(error.provider_account_ref, None);
assert_eq!(error.upstream_transport, None);
assert_eq!(error.upstream_status_code, None);
assert_eq!(error.upstream_request_id, None);
assert_eq!(error.error_details, None);
// 路由入口不等于已选择 Provider;没有平台事实时不能被平台/attempt 筛选命中
for filter in [
OpsErrorFilter {
provider_kind: Some("openai".to_owned()),
..query.filter.clone()
},
OpsErrorFilter {
attempt_index: Some(1),
..query.filter.clone()
},
] {
let page = repository
.list_ops_errors(OpsErrorQuery {
filter,
..query.clone()
})
.await
.expect("filter absent upstream facts");
assert_eq!(page.total, 0);
assert!(page.items.is_empty());
}
let admin = admin_observability_store(&database.pool);
let admin_detail = admin
.usage_record_detail(request.id.as_str())
.await
.expect("admin detail");
assert_eq!(admin_detail.trace, Some(trace));
assert_eq!(admin_detail.request.attempt_count, 0);
assert!(admin_detail.attempts.is_empty());
let admin_errors = admin
.list_ops_errors(admin_observability::OpsErrorQuery {
range: admin_observability::TimeRange::new(query.range.start, query.range.end)
.expect("admin time range"),
filter: admin_observability::OpsErrorFilter {
request_id: Some(request.id.as_str().to_owned()),
..Default::default()
},
current_page: 1,
page_size: ObservabilityPageSize::new(10).expect("page size"),
})
.await
.expect("admin ops errors without provider facts");
assert_eq!(admin_errors.total, 1);
assert_eq!(admin_errors.items.len(), 1);
assert_eq!(admin_errors.items[0].attempt_index, None);
assert_eq!(admin_errors.items[0].provider_kind, None);
database.close().await;
}
#[tokio::test]
async fn zero_attempt_finalization_rejects_sent_or_missing_attempt_facts_atomically() {
let Some(database) = TestDatabase::create("zero_attempt_constraints").await else {
return;
};
let store = PgExecutionStore::new(database.pool.clone());
let request = accepted_request("req_zero_attempt_constraints");
store
.create_model_request(request.clone())
.await
.expect("create request");
let before = stored_row(&database.pool, request.id.as_str()).await;
for (count, send_state) in [
(0, UpstreamSendState::Sent),
(0, UpstreamSendState::Ambiguous),
(1, UpstreamSendState::NotSent),
] {
let mut finalization = early_failure(&request);
finalization.attempt_count = count;
finalization.send_state = send_state;
assert!(
ExecutionStore::finalize_model_request(&store, finalization)
.await
.is_err()
);
assert_eq!(
stored_row(&database.pool, request.id.as_str()).await,
before
);
}
for invalid_trace in [
"not JSON".to_owned(),
"[]".to_owned(),
json!({"data": "x".repeat(MAX_TRACE_SNAPSHOT_BYTES)}).to_string(),
] {
let mut finalization = early_failure(&request);
finalization.diagnostic_trace_json = Some(invalid_trace);
assert_eq!(
ExecutionStore::finalize_model_request(&store, finalization)
.await
.expect_err("invalid trace")
.kind(),
StoreErrorKind::InvalidData,
);
assert_eq!(
stored_row(&database.pool, request.id.as_str()).await,
before
);
}
ExecutionStore::finalize_model_request(&store, early_failure(&request))
.await
.expect("valid zero attempt");
database.close().await;
}
#[tokio::test]
async fn zero_attempt_duplicate_create_and_finalization_never_overwrite_terminal_facts() {
let Some(database) = TestDatabase::create("zero_attempt_idempotency").await else {
return;
};
let store = PgExecutionStore::new(database.pool.clone());
let request = accepted_request("req_zero_attempt_idempotency");
store
.create_model_request(request.clone())
.await
.expect("create request");
let (first, second) = tokio::join!(
ExecutionStore::finalize_model_request(&store, early_failure(&request)),
ExecutionStore::finalize_model_request(&store, early_failure(&request)),
);
assert_eq!(usize::from(first.is_ok()) + usize::from(second.is_ok()), 1);
assert_eq!(
first
.err()
.or_else(|| second.err())
.expect("CAS loser")
.kind(),
StoreErrorKind::InvalidState
);
let terminal = stored_row(&database.pool, request.id.as_str()).await;
assert!(store.create_model_request(request.clone()).await.is_err());
let mut late = early_failure(&request);
late.outcome = ExecutionOutcome::Cancelled;
late.error = Some(GatewayError::new(
GatewayErrorKind::Cancelled,
"request was cancelled",
));
late.diagnostic_trace_json = None;
late.completed_at += StdDuration::from_secs(1);
assert_eq!(
ExecutionStore::finalize_model_request(&store, late)
.await
.expect_err("already finalized")
.kind(),
StoreErrorKind::InvalidState
);
assert_eq!(
store
.recover_expired(request.deadline_at.at().unwrap())
.await
.expect("recover")
.requests,
0
);
assert_eq!(
stored_row(&database.pool, request.id.as_str()).await,
terminal
);
let count: i64 = sqlx::query_scalar("select count(*) from model_requests")
.fetch_one(&database.pool)
.await
.expect("one request row");
assert_eq!(count, 1);
let ops: i64 = sqlx::query_scalar("select count(*) from ops_events")
.fetch_one(&database.pool)
.await
.expect("no extra error ledger");
assert_eq!(ops, 0);
database.close().await;
}
#[tokio::test]
async fn zero_attempt_recovery_respects_deadline_and_does_not_invent_a_trace() {
let Some(database) = TestDatabase::create("zero_attempt_recovery").await else {
return;
};
let store = PgExecutionStore::new(database.pool.clone());
let request = accepted_request("req_zero_attempt_recovery");
let mut active = request.clone();
active.id = ModelRequestId::new("req_zero_attempt_still_running").expect("request id");
active.deadline_at = (active.deadline_at.at().unwrap() + StdDuration::from_secs(30)).into();
store
.create_model_request(request.clone())
.await
.expect("create interrupted request");
store
.create_model_request(active.clone())
.await
.expect("create active request");
assert_eq!(
store
.recover_expired(request.deadline_at.at().unwrap() - StdDuration::from_micros(1))
.await
.expect("before deadline")
.requests,
0
);
assert_eq!(
store
.recover_expired(request.deadline_at.at().unwrap())
.await
.expect("at deadline")
.requests,
1
);
let recovered = stored_row(&database.pool, request.id.as_str()).await;
assert_eq!(
store
.recover_expired(request.deadline_at.at().unwrap())
.await
.expect("repeat recovery")
.requests,
0
);
assert_eq!(
ExecutionStore::finalize_model_request(&store, early_failure(&request))
.await
.expect_err("late finalize after recovery")
.kind(),
StoreErrorKind::InvalidState
);
assert_eq!(
stored_row(&database.pool, request.id.as_str()).await,
recovered
);
let repository = observability_repository(&database.pool);
let detail = repository
.usage_record_detail(request.id.as_str())
.await
.expect("recovered detail");
assert_eq!(detail.request.outcome.as_str(), "incomplete");
assert_eq!(
detail.request.error_kind.as_deref(),
Some("process_interrupted")
);
assert_eq!(
detail.request.completed_at,
Some(DateTime::from(request.deadline_at.at().unwrap()))
);
assert_eq!(detail.request.attempt_count, 0);
assert_eq!(detail.request.upstream_send_state, "not_sent");
assert_eq!(detail.trace, None);
assert!(detail.attempts.is_empty());
let errors = repository
.list_ops_errors(OpsErrorQuery {
range: zero_attempt_range(&request),
filter: OpsErrorFilter::default(),
current_page: 1,
page_size: ObservabilityPageSize::new(10).expect("page size"),
})
.await
.expect("recovered ops error");
assert_eq!(errors.total, 1);
assert_eq!(errors.items[0].failure_kind, "process_interrupted");
assert_eq!(errors.items[0].attempt_index, None);
assert_eq!(
repository
.usage_record_detail(active.id.as_str())
.await
.expect("active detail")
.request
.outcome
.as_str(),
"running"
);
database.close().await;
}
#[tokio::test]
async fn zero_attempt_failure_does_not_enter_successful_usage_or_cost_aggregates() {
let Some(database) = TestDatabase::create("zero_attempt_metrics").await else {
return;
};
let store = PgExecutionStore::new(database.pool.clone());
let request = accepted_request("req_zero_attempt_metrics");
seed_running_request(&database.pool, "req_zero_attempt_success_control")
.await
.expect("success control");
let mut success = successful_core_finalization("req_zero_attempt_success_control");
success.downstream_committed_at = Some(success.completed_at);
success.usage = Usage {
input_tokens: Some(100),
output_tokens: Some(20),
total_tokens: Some(120),
..Usage::new()
};
success.timings.latency_ms = Some(200);
success.cost = gateway_core::metering::CalculatedCost::from_usd_ticks(12_500_000_000)
.expect("calculated cost")
.into_estimate();
ExecutionStore::finalize_model_request(&store, success)
.await
.expect("finalize success control");
let repository = observability_repository(&database.pool);
super::drain_analytics(&database.pool).await;
let before = repository
.usage_insights_observation(
zero_attempt_range(&request),
UsageRecordFilter::default(),
gateway_admin::model::observability::Granularity::Hour,
true,
)
.await
.map(|snapshot| snapshot.overview)
.expect("baseline usage");
assert_eq!(before.requests.total_tokens, 120);
assert!(!before.attempts.costs.is_empty());
store
.create_model_request(request.clone())
.await
.expect("create zero attempt");
ExecutionStore::finalize_model_request(&store, early_failure(&request))
.await
.expect("finalize failure");
super::drain_analytics(&database.pool).await;
let after = repository
.usage_insights_observation(
zero_attempt_range(&request),
UsageRecordFilter::default(),
gateway_admin::model::observability::Granularity::Hour,
true,
)
.await
.map(|snapshot| snapshot.overview)
.expect("usage after failure");
assert_eq!(
after.requests.request_count,
before.requests.request_count + 1
);
assert_eq!(
after.requests.failure_count,
before.requests.failure_count + 1
);
assert_eq!(after.requests.success_count, before.requests.success_count);
assert_eq!(after.requests.total_tokens, before.requests.total_tokens);
assert_eq!(
after.requests.latency_sum_ms,
before.requests.latency_sum_ms
);
assert_eq!(after.requests.latency_count, before.requests.latency_count);
assert_eq!(after.attempts, before.attempts);
let successes = repository
.usage_insights_observation(
zero_attempt_range(&request),
UsageRecordFilter {
outcome: Some("succeeded".to_owned()),
..Default::default()
},
gateway_admin::model::observability::Granularity::Hour,
true,
)
.await
.map(|snapshot| snapshot.overview)
.expect("successful aggregation unchanged");
assert_eq!(successes.range, before.range);
assert_eq!(successes.requests, before.requests);
assert_eq!(successes.attempts, before.attempts);
assert_eq!(successes.providers, before.providers);
assert_eq!(
successes.analytics.schema_version,
before.analytics.schema_version
);
assert_eq!(
successes.analytics.histogram_version,
before.analytics.histogram_version
);
assert!(successes.analytics.generation >= before.analytics.generation);
assert!(successes.analytics.generated_at >= before.analytics.generated_at);
assert!(successes.analytics.coverage_start.is_some());
assert!(successes.analytics.coverage_end >= before.analytics.coverage_end);
let filter = UsageRecordFilter {
request_id: Some(request.id.as_str().to_owned()),
..Default::default()
};
let failed = repository
.usage_insights_observation(
zero_attempt_range(&request),
filter.clone(),
gateway_admin::model::observability::Granularity::Hour,
true,
)
.await
.map(|snapshot| snapshot.overview)
.expect("failed request metrics");
assert_eq!(failed.requests.request_count, 1);
assert_eq!(failed.requests.failure_count, 1);
assert_eq!(failed.requests.total_tokens, 0);
assert_eq!(failed.requests.latency_count, 0);
assert_eq!(
failed.attempts,
admin_observability::AttemptMetrics::default()
);
let page = repository
.list_usage_records(UsageRecordQuery {
status: Default::default(),
range: zero_attempt_range(&request),
filter: filter.clone(),
current_page: 1,
page_size: ObservabilityPageSize::new(10).expect("page size"),
})
.await
.expect("successful usage list excludes failure");
assert_eq!(page.total, 0);
assert!(page.items.is_empty());
super::drain_analytics(&database.pool).await;
let insights = repository
.usage_insights_observation(
zero_attempt_range(&request),
filter,
admin_observability::Granularity::FifteenMinutes,
true,
)
.await
.expect("billing projection");
assert!(
insights
.costs
.iter()
.all(|point| { point.standard_amount.is_none() && point.no_cache_amount.is_none() })
);
let charges: i64 = sqlx::query_scalar("select count(*) from client_api_key_charge_events")
.fetch_one(&database.pool)
.await
.expect("observations do not settle budgets");
assert_eq!(charges, 0);
database.close().await;
}
#[tokio::test]
async fn entry_rejection_is_visible_without_a_fictitious_model_execution() {
let Some(database) = TestDatabase::create("entry_rejection").await else {
return;
};
let repository = PgExecutionStore::new(database.pool.clone());
repository
.record_entry_rejection(gateway_core::engine::EntryRejection {
request_id: ModelRequestId::new("req_entry_rejection").unwrap(),
client_key_id: ClientApiKeyId::new("key_entry").unwrap(),
error: GatewayError::new(GatewayErrorKind::NoAvailableProvider, "no route"),
latency: StdDuration::from_millis(12),
})
.await
.unwrap();
let row: (Option<String>, String, String, i64) = sqlx::query_as(
"select model_request_id, failure_kind, message, latency_ms from ops_events where component = 'request_entry' and operation = 'reject'"
).fetch_one(&database.pool).await.unwrap();
assert!(row.0.is_none());
assert_eq!(row.1, GatewayErrorKind::NoAvailableProvider.as_str());
assert_eq!(
serde_json::from_str::<Value>(&row.2).unwrap()["requestId"],
"req_entry_rejection"
);
assert_eq!(row.3, 12);
let count: i64 = sqlx::query_scalar("select count(*) from model_requests")
.fetch_one(&database.pool)
.await
.unwrap();
assert_eq!(count, 0);
database.close().await;
}
#[tokio::test]
async fn shared_core_usage_and_timings_preserve_fields_and_reject_invalid_phases() {
let Some(database) = TestDatabase::create("shared_execution_facts").await else {
return;
};
let id = "req_shared_facts";
seed_running_request(&database.pool, id).await.unwrap();
let store = PgExecutionStore::new(database.pool.clone());
let mut finalization = successful_core_finalization(id);
finalization.usage = Usage {
input_tokens: Some(101),
output_tokens: Some(22),
cached_input_tokens: Some(33),
cache_write_input_tokens: Some(44),
reasoning_output_tokens: Some(5),
image_input_tokens: Some(6),
image_output_tokens: Some(7),
total_tokens: Some(123),
};
finalization.timings = CoreModelRequestTimings {
transport_decision_wait_ms: Some(1),
connect_ms: Some(2),
headers_ms: Some(3),
first_event_ms: Some(4),
first_reasoning_ms: Some(5),
first_text_ms: Some(6),
first_token_ms: Some(7),
provider_processing_ms: Some(8),
upstream_response_ms: Some(1_000),
upstream_api_overhead_ms: Some(120.25),
upstream_engine_ms: Some(6400.0),
latency_ms: Some(9),
};
ExecutionStore::finalize_model_request(&store, finalization)
.await
.unwrap();
let persisted = stored_row(&database.pool, id).await;
for (field, expected) in [
("input_tokens", 101),
("output_tokens", 22),
("cached_input_tokens", 33),
("cache_write_input_tokens", 44),
("reasoning_output_tokens", 5),
("image_input_tokens", 6),
("image_output_tokens", 7),
("total_tokens", 123),
("transport_decision_wait_ms", 1),
("connect_ms", 2),
("headers_ms", 3),
("first_event_ms", 4),
("first_reasoning_ms", 5),
("first_text_ms", 6),
("first_token_ms", 7),
("provider_processing_ms", 8),
("upstream_response_ms", 1_000),
("latency_ms", 9),
] {
assert_eq!(persisted[field], json!(expected), "{field}");
}
assert_eq!(persisted["upstream_api_overhead_ms"].as_f64(), Some(120.25));
assert_eq!(persisted["upstream_engine_ms"].as_f64(), Some(6400.0));
let invalid_id = "req_invalid_phase";
seed_running_request(&database.pool, invalid_id)
.await
.unwrap();
let before = stored_row(&database.pool, invalid_id).await;
let mut invalid = successful_core_finalization(invalid_id);
invalid.timings.latency_ms = Some(10);
invalid.timings.first_token_ms = Some(11);
let error = ExecutionStore::finalize_model_request(&store, invalid)
.await
.unwrap_err();
assert_eq!(error.kind(), StoreErrorKind::InvalidData);
assert_eq!(stored_row(&database.pool, invalid_id).await, before);
for invalid_ms in [-1.0, f64::NAN, f64::INFINITY, i64::MAX as f64] {
let mut invalid = successful_core_finalization(invalid_id);
invalid.timings.upstream_engine_ms = Some(invalid_ms);
let error = ExecutionStore::finalize_model_request(&store, invalid)
.await
.expect_err("invalid official timing must not silently become missing");
assert_eq!(error.kind(), StoreErrorKind::InvalidData);
assert_eq!(stored_row(&database.pool, invalid_id).await, before);
}
database.close().await;
}
#[tokio::test]
async fn retry_after_transport_commit_preserves_delivery_facts_and_attempt_order() {
let Some(database) = TestDatabase::create("retry_after_commit").await else {
return;
};
let store = PgExecutionStore::new(database.pool.clone());
for (transport, status) in [("websocket", None), ("http_sse", Some(200))] {
let id = format!("req_retry_after_{transport}_commit");
let mut request = accepted_request(&id);
request.client_transport = transport.to_owned();
store.create_model_request(request).await.unwrap();
let mut attempt = ModelRequestAttemptStart {
model_request_id: id.clone(),
attempt_count: 1,
provider_kind: "openai".to_owned(),
account_snapshot: None,
provider_account_ref: Some("account_before_retry".to_owned()),
upstream_model_id: Some("model".to_owned()),
upstream_transport: "websocket".to_owned(),
http_version: Some("HTTP/1.1".to_owned()),
account_selection_wait_ms: Some(5),
capacity_used_slots: Some(1),
capacity_total_slots: Some(10),
};
store
.begin_model_request_attempt(attempt.clone())
.await
.unwrap();
store
.mark_upstream_send_state(&id, gateway_store::postgres::UpstreamSendState::Sent)
.await
.unwrap();
// 可重放的 WS 前导事件或 HTTP 心跳先交付,不能替代 Core 的业务重放屏障
let committed_at = Utc::now();
ModelRequestRepository::mark_downstream_committed(&store, &id, committed_at, status)
.await
.unwrap();
let committed = stored_row(&database.pool, &id).await;
attempt.attempt_count = 2;
attempt.provider_account_ref = Some("account_after_retry".to_owned());
attempt.upstream_transport = "http_sse".to_owned();
attempt.http_version = Some("HTTP/2".to_owned());
attempt.account_selection_wait_ms = Some(7);
attempt.capacity_used_slots = Some(2);
assert_eq!(
store
.begin_model_request_attempt(attempt.clone())
.await
.unwrap(),
2
);
let retried = stored_row(&database.pool, &id).await;
assert_eq!(
retried["downstream_committed_at"],
committed["downstream_committed_at"]
);
assert_eq!(
retried["client_status_code"],
committed["client_status_code"]
);
assert_eq!(retried["upstream_send_state"], "sent");
assert_eq!(retried["provider_account_ref"], "account_after_retry");
assert_eq!(retried["upstream_transport"], "http_sse");
assert_eq!(
retried["request_observation_json"]["scheduling"]["accountSelectionWaitMs"],
12
);
assert_eq!(
retried["request_observation_json"]["scheduling"]["capacityUsedSlots"],
2
);
assert_eq!(
retried["request_observation_json"]["transport"]["httpVersion"],
"HTTP/2"
);
// 已接收的序号与跳号都不能覆盖新 attempt 的账号和计时
for count in [2, 4] {
attempt.attempt_count = count;
let error = store
.begin_model_request_attempt(attempt.clone())
.await
.unwrap_err();
assert!(matches!(
error,
gateway_store::StoreError::Conflict {
kind: gateway_store::ConflictKind::InvalidTransition,
..
}
));
assert_eq!(stored_row(&database.pool, &id).await, retried);
}
let mut finalization = successful_core_finalization(&id);
finalization.attempt_count = 2;
finalization.downstream_committed_at = Some(committed_at.into());
finalization.client_status_code = status;
finalization.upstream_transport = Some("http_sse".to_owned());
ExecutionStore::finalize_model_request(&store, finalization)
.await
.unwrap();
let terminal = stored_row(&database.pool, &id).await;
assert_eq!(terminal["outcome"], "succeeded");
assert_eq!(terminal["attempt_count"], 2);
assert_eq!(
terminal["request_observation_json"]["scheduling"],
retried["request_observation_json"]["scheduling"]
);
attempt.attempt_count = 3;
assert!(store.begin_model_request_attempt(attempt).await.is_err());
assert_eq!(stored_row(&database.pool, &id).await, terminal);
}
database.close().await;
}
#[tokio::test]
async fn concurrent_retry_updates_keep_request_context_and_clear_previous_transport() {
let Some(database) = TestDatabase::create("observation_retry_cas").await else {
return;
};
let store = PgExecutionStore::new(database.pool.clone());
let request = accepted_request("req_observation_retry");
store.create_model_request(request.clone()).await.unwrap();
let before = stored_row(&database.pool, request.id.as_str()).await;
let mut attempt = ModelRequestAttemptStart {
model_request_id: request.id.as_str().to_owned(),
attempt_count: 1,
provider_kind: "openai".to_owned(),
account_snapshot: None,
provider_account_ref: Some("deleted_account".to_owned()),
upstream_model_id: Some("model".to_owned()),
upstream_transport: "http_sse".to_owned(),
http_version: Some("HTTP/1.1".to_owned()),
account_selection_wait_ms: Some(5),
capacity_used_slots: Some(1),
capacity_total_slots: Some(10),
};
store
.begin_model_request_attempt(attempt.clone())
.await
.unwrap();
sqlx::query("update model_requests set request_observation_json = request_observation_json || $1::jsonb where id = $2")
.bind(sqlx::types::Json(
json!({"transport": {"httpVersion":"HTTP/1.1", "connection": {
"id":"previous", "exitReason":"peer_closed", "ageMs":10, "idleMs":0
}}}),
))
.bind(request.id.as_str())
.execute(&database.pool)
.await
.unwrap();
attempt.attempt_count = 2;
attempt.http_version = Some("HTTP/2".to_owned());
attempt.account_selection_wait_ms = Some(7);
attempt.capacity_used_slots = Some(2);
let (first, second) = tokio::join!(
store.begin_model_request_attempt(attempt.clone()),
store.begin_model_request_attempt(attempt),
);
assert_eq!(usize::from(first.is_ok()) + usize::from(second.is_ok()), 1);
let retried = stored_row(&database.pool, request.id.as_str()).await;
let observation = &retried["request_observation_json"];
assert_eq!(
observation["request"],
before["request_observation_json"]["request"]
);
assert_eq!(
observation["routing"],
before["request_observation_json"]["routing"]
);
assert_eq!(observation["scheduling"]["admissionDecisionMs"], 2);
assert_eq!(observation["scheduling"]["accountSelectionWaitMs"], 12);
assert_eq!(observation["scheduling"]["capacityUsedSlots"], 2);
assert_eq!(observation["transport"], json!({"httpVersion":"HTTP/2"}));
let mut finalization = successful_core_finalization(request.id.as_str());
finalization.attempt_count = 2;
finalization.http_version = None;
finalization.timings.upstream_response_ms = Some(1_000);
ExecutionStore::finalize_model_request(&store, finalization)
.await
.unwrap();
let terminal = stored_row(&database.pool, request.id.as_str()).await;
assert_eq!(
terminal["request_observation_json"]["scheduling"],
observation["scheduling"]
);
assert_eq!(
terminal["request_observation_json"]["request"],
observation["request"]
);
assert_eq!(
terminal["request_observation_json"]["transport"]["httpVersion"],
"HTTP/2"
);
assert_eq!(
terminal["request_observation_json"]["timings"]["upstream"]["responseMs"],
1_000
);
database.close().await;
}