fix(openai): drop placeholder secondary quota windows

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Zyy
2026-08-04 22:30:04 +08:00
co-authored by Codex
parent eabb61b08c
commit d388fa344a
3 changed files with 247 additions and 16 deletions
@@ -430,7 +430,10 @@ impl CodexCredentialQuotaService {
.and_then(|observation| observation.quota)
.map(OpaqueProviderData::into_inner)
.unwrap_or_default();
let quota = confirmed_exhaustion_projection(existing, reset_at);
let quota = confirmed_exhaustion_projection(
normalize_quota_window_placeholders(existing),
reset_at,
);
let observed_at = SystemTime::now();
let outcome = self
.store
@@ -594,10 +597,12 @@ impl CodexCredentialQuotaService {
let refreshed_fact = refreshed_snapshot.fact();
let previous_reset_at = self.previous_confirmed_reset(account).await;
let now = SystemTime::now();
let object = value
.as_object()
.cloned()
.ok_or(CodexCredentialQuotaError::InvalidCredentialData)?;
let object = normalize_quota_window_placeholders(
value
.as_object()
.cloned()
.ok_or(CodexCredentialQuotaError::InvalidCredentialData)?,
);
let object = if account.availability() == AccountAvailability::QuotaExhausted
&& quota_success_state(
account.availability(),
@@ -927,10 +932,12 @@ impl CodexCredentialQuotaService {
let refreshed_fact = refreshed_snapshot.fact();
let previous_reset_at = self.previous_confirmed_reset(&account).await;
let now = SystemTime::now();
let object = value
.as_object()
.cloned()
.ok_or(CodexCredentialQuotaError::InvalidCredentialData)?;
let object = normalize_quota_window_placeholders(
value
.as_object()
.cloned()
.ok_or(CodexCredentialQuotaError::InvalidCredentialData)?,
);
let object = if account.availability() == AccountAvailability::QuotaExhausted
&& quota_success_state(
account.availability(),
@@ -1181,6 +1188,40 @@ fn merge_passive_quota(
quota
}
/// 丢弃 core 限额中上游给出的无事实 `secondary_window` 占位。
///
/// 生产响应头可能携带 `used_percent=0`、零时长和空 reset 的占位。正常被动同步
/// 保留该响应事实,Admin 展示会将其隐藏;但主动 `/usage` 刷新或 402 确认投影若
/// 遇到这个对象,会把它写成 100%,从而显示并不存在的“次级额度”。因此这些
/// 非被动写入口在落库前移除无事实对象。带 reset、时长、正用量、触顶、未知或
/// 非法字段的次级窗口均完全按原有额度逻辑保留。
fn normalize_quota_window_placeholders(mut quota: Map<String, Value>) -> Map<String, Value> {
if let Some(rate_limit) = quota.get_mut("rate_limit").and_then(Value::as_object_mut) {
drop_secondary_window_placeholder(rate_limit);
}
quota
}
fn drop_secondary_window_placeholder(rate_limit: &mut Map<String, Value>) {
let placeholder = rate_limit
.get("secondary_window")
.and_then(Value::as_object)
.is_some_and(secondary_window_is_placeholder);
if placeholder {
rate_limit.remove("secondary_window");
}
}
fn secondary_window_is_placeholder(window: &Map<String, Value>) -> bool {
window.iter().all(|(field, value)| match field.as_str() {
"used_percent" => value
.as_f64()
.is_some_and(|used_percent| used_percent.is_finite() && used_percent == 0.0),
"limit_reached" => value.as_bool() == Some(false),
_ => false,
})
}
/// 用已确认的上游额度拒绝覆盖 core 限额投影。
///
/// 上游 usage 快照可能在结算期间短暂滞后为 98/99% 或 `allowed=true`;保留其他
@@ -424,14 +424,71 @@ async fn passive_rate_limit_headers_replace_stale_secondary_window() {
.await
.expect("read quota")
.expect("quota snapshot");
let raw = store
.quota_json("acct_passive_quota_snapshot")
.expect("stored passive quota");
let monthly = snapshot
.windows()
.iter()
.filter(|window| window.kind() == CodexQuotaWindowKind::Monthly)
.collect::<Vec<_>>();
assert_eq!(snapshot.windows().len(), 1);
assert_eq!(monthly.len(), 1);
assert_eq!(monthly[0].used_percent(), Some(92.0));
assert!(
raw.pointer("/rate_limit/secondary_window").is_none(),
"a normal response without a secondary window must delete stale raw state"
);
}
#[tokio::test]
async fn manual_quota_refresh_drops_empty_secondary_window() {
let store = Arc::new(MemoryAccountStore::default());
let account_id = "acct_manual_secondary_placeholder";
create_account(&store, account_id).await;
let account = store.account(account_id).expect("created account");
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/codex/usage"))
.and(header(
"authorization",
format!("Bearer token-{account_id}"),
))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"rate_limit": {
"allowed": false,
"limit_reached": true,
"primary_window": {
"used_percent": 100,
"reset_at": 1_900_000_000,
"limit_window_seconds": 2_592_000
},
"secondary_window": {
"used_percent": 0,
"limit_reached": false
}
}
})))
.expect(1)
.mount(&server)
.await;
let service = quota_service_with_base_url(
&store,
reqwest::Client::builder()
.no_proxy()
.build()
.expect("client"),
server.uri(),
);
let snapshot = service
.refresh_account(account.id())
.await
.expect("manual quota refresh");
assert_eq!(snapshot.windows().len(), 1);
assert_eq!(snapshot.windows()[0].used_percent(), Some(100.0));
}
#[tokio::test]
@@ -719,6 +776,127 @@ async fn periodic_quota_synchronization_does_not_downgrade_exhaustion_on_rate_li
assert_eq!(account.availability(), AccountAvailability::QuotaExhausted);
}
#[tokio::test]
async fn periodic_quota_refresh_drops_empty_secondary_window() {
let store = Arc::new(MemoryAccountStore::default());
let account_id = "acct_periodic_secondary_placeholder";
create_account(&store, account_id).await;
let account = store.account(account_id).expect("created account");
store
.apply_state_change(AccountStateChange {
message: None,
account_id: account.id().clone(),
expected_revision: account.revision(),
availability: AccountAvailability::QuotaExhausted,
observed_at: SystemTime::now(),
})
.await
.expect("mark account exhausted");
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/codex/usage"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"rate_limit": {
"allowed": true,
"limit_reached": false,
"primary_window": {
"used_percent": 99,
"reset_at": 1_900_000_000,
"limit_window_seconds": 2_592_000
},
"secondary_window": {}
}
})))
.expect(1)
.mount(&server)
.await;
let service = quota_service_with_base_url(
&store,
reqwest::Client::builder()
.no_proxy()
.build()
.expect("client"),
server.uri(),
);
let summary = service.synchronize().await.expect("periodic quota refresh");
let snapshot = service
.read_account(account.id())
.await
.expect("read quota")
.expect("quota snapshot");
assert_eq!(summary.exhausted, 1);
assert_eq!(snapshot.windows().len(), 1);
assert_eq!(snapshot.windows()[0].used_percent(), Some(100.0));
}
#[tokio::test]
async fn periodic_quota_refresh_keeps_nonempty_secondary_window_during_confirmed_exhaustion() {
let store = Arc::new(MemoryAccountStore::default());
let account_id = "acct_periodic_real_secondary";
create_account(&store, account_id).await;
let account = store.account(account_id).expect("created account");
store
.apply_state_change(AccountStateChange {
message: None,
account_id: account.id().clone(),
expected_revision: account.revision(),
availability: AccountAvailability::QuotaExhausted,
observed_at: SystemTime::now(),
})
.await
.expect("mark account exhausted");
let secondary_reset_at = 1_900_604_800;
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/codex/usage"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"rate_limit": {
"allowed": true,
"limit_reached": false,
"primary_window": {
"used_percent": 99,
"reset_at": 1_900_000_000,
"limit_window_seconds": 2_592_000
},
"secondary_window": {
"used_percent": 0,
"reset_at": secondary_reset_at,
"limit_window_seconds": 604_800
}
}
})))
.expect(1)
.mount(&server)
.await;
let service = quota_service_with_base_url(
&store,
reqwest::Client::builder()
.no_proxy()
.build()
.expect("client"),
server.uri(),
);
let summary = service.synchronize().await.expect("periodic quota refresh");
let snapshot = service
.read_account(account.id())
.await
.expect("read quota")
.expect("quota snapshot");
assert_eq!(summary.exhausted, 1);
assert_eq!(snapshot.windows().len(), 2);
assert!(snapshot.windows().iter().any(|window| {
window.window_seconds() == Some(604_800)
&& window.used_percent() == Some(100.0)
&& window
.reset_at()
.is_some_and(|reset_at| reset_at.timestamp() == secondary_reset_at)
}));
}
#[tokio::test]
async fn periodic_quota_synchronization_respects_retry_after_on_service_unavailable() {
let store = Arc::new(MemoryAccountStore::default());
@@ -1437,7 +1437,8 @@ async fn official_usage_limit_failure_projects_100_percent_and_holds_until_reset
"rate_limit": {
"allowed": true,
"limit_reached": false,
"primary_window": {"used_percent": 99, "reset_at": reset_at}
"primary_window": {"used_percent": 99, "reset_at": reset_at},
"secondary_window": {"used_percent": 0}
}
})
.as_object()
@@ -1463,7 +1464,8 @@ async fn official_usage_limit_failure_projects_100_percent_and_holds_until_reset
"rate_limit": {
"allowed": true,
"limit_reached": false,
"primary_window": {"used_percent": 99, "reset_at": reset_at}
"primary_window": {"used_percent": 99, "reset_at": reset_at},
"secondary_window": {"used_percent": 0}
}
})),
)
@@ -1512,16 +1514,22 @@ async fn official_usage_limit_failure_projects_100_percent_and_holds_until_reset
.next()
.expect("confirmed quota observation");
let projected_observed_at = projected.observed_at.expect("projection observed at");
let projected = projected
.quota
.map(OpaqueProviderData::into_inner)
.expect("confirmed quota projection");
let projected = Value::Object(
projected
.quota
.map(OpaqueProviderData::into_inner)
.expect("confirmed quota projection"),
);
assert_eq!(
Value::Object(projected)
projected
.pointer("/rate_limit/primary_window/used_percent")
.and_then(Value::as_u64),
Some(100)
);
assert!(
projected.pointer("/rate_limit/secondary_window").is_none(),
"request failure must not manufacture a secondary quota"
);
tokio::time::sleep(Duration::from_millis(200)).await;
let requests = server.received_requests().await.expect("received requests");
assert_eq!(
@@ -1572,6 +1580,10 @@ async fn official_usage_limit_failure_projects_100_percent_and_holds_until_reset
.and_then(Value::as_i64),
Some(reset_at)
);
assert!(
quota.pointer("/rate_limit/secondary_window").is_none(),
"background quota refresh must retain the same window projection"
);
let requests = server.received_requests().await.expect("received requests");
assert_eq!(
requests