fix(grok): free 500k + 对齐 personal-dev tier/时间窗

- soft-gate 默认额度 500k tokens / 滚动 24h / 95%(门禁 475k)
- soft-gate 仅显式 free OAuth(subscription_tier/plan_type == free)
- isKnownGrokFreeAccount 按 personal-dev(usage% 为 paid 证据、仅 credentials tier)
- 调度阈值仅 grok_sched header quota 窗,去掉 billing 7d/30d 候选
This commit is contained in:
IanShaw027
2026-08-07 18:52:53 +08:00
parent 8f5657b91c
commit 72a56f862c
10 changed files with 95 additions and 173 deletions
+2 -2
View File
@@ -2343,8 +2343,8 @@ func setDefaults() {
// Enabled by default because free detection requires an explicit free tier marker.
viper.SetDefault("gateway.grok.free_quota_soft_gate_enabled", true)
viper.SetDefault("gateway.grok.password_auth_enabled", false)
// Align soft-gate nominal limit with current Free rolling quota (1M tokens / 24h).
viper.SetDefault("gateway.grok.free_quota_token_limit", int64(1_000_000))
// Free soft-gate nominal limit: 500k tokens / rolling 24h (operator policy).
viper.SetDefault("gateway.grok.free_quota_token_limit", int64(500_000))
viper.SetDefault("gateway.grok.free_quota_soft_gate_percent", 95)
viper.SetDefault("gateway.grok.free_quota_window_hours", 24)
viper.SetDefault("gateway.grok.free_quota_stats_cache_seconds", 5)
+1 -1
View File
@@ -545,7 +545,7 @@ func TestLoadDefaultGrokFreeQuotaSoftGate(t *testing.T) {
require.NoError(t, err)
require.False(t, cfg.Gateway.Grok.PasswordAuthEnabled)
require.True(t, cfg.Gateway.Grok.FreeQuotaSoftGateEnabled)
require.Equal(t, int64(1_000_000), cfg.Gateway.Grok.FreeQuotaTokenLimit)
require.Equal(t, int64(500_000), cfg.Gateway.Grok.FreeQuotaTokenLimit)
require.Equal(t, 95, cfg.Gateway.Grok.FreeQuotaSoftGatePercent)
require.Equal(t, 24, cfg.Gateway.Grok.FreeQuotaWindowHours)
require.Equal(t, 5, cfg.Gateway.Grok.FreeQuotaStatsCacheSeconds)
+5 -1
View File
@@ -7,10 +7,14 @@ import (
"time"
)
const GrokFreeRolling24hTokenLimit int64 = 1_000_000
// GrokFreeRolling24hTokenLimit is the operator soft-gate nominal Free allowance
// (rolling 24h). Soft-gate default matches this; upstream header limits may
// still report historical 1M/2M Free snapshots.
const GrokFreeRolling24hTokenLimit int64 = 500_000
var grokFreeRolling24hTokenLimits = map[int64]struct{}{
GrokFreeRolling24hTokenLimit: {},
1_000_000: {}, // Observed Free limit variants.
2_000_000: {}, // Legacy Free limit observed before July 2026.
}
+2
View File
@@ -165,6 +165,8 @@ func TestIsGrokFreeRolling24hTokenLimit(t *testing.T) {
t.Parallel()
require.True(t, IsGrokFreeRolling24hTokenLimit(GrokFreeRolling24hTokenLimit))
require.True(t, IsGrokFreeRolling24hTokenLimit(500_000))
require.True(t, IsGrokFreeRolling24hTokenLimit(1_000_000), "observed Free limit variants remain classifiable")
require.True(t, IsGrokFreeRolling24hTokenLimit(2_000_000), "legacy snapshots remain classifiable")
require.False(t, IsGrokFreeRolling24hTokenLimit(3_000_000))
}
@@ -282,11 +282,15 @@ func anthropicThresholdCandidates(account *Account) []*accountSchedulingThreshol
// kiro_sched_* / antigravity_sched_* extras are still written purely as
// observability snapshots.
// grokThresholdCandidates mirrors personal-dev: only header-projected
// grok_sched_utilization / grok_sched_reset_at (rolling quota window, reset
// capped at ~25h when written). Official billing 7d/30d windows are not used
// for auto-pause here.
func grokThresholdCandidates(account *Account) []*accountSchedulingThresholdCandidate {
if account == nil {
return nil
}
candidates := []*accountSchedulingThresholdCandidate{
return []*accountSchedulingThresholdCandidate{
{
window: "quota",
scope: "grok",
@@ -294,62 +298,6 @@ func grokThresholdCandidates(account *Account) []*accountSchedulingThresholdCand
until: parseSchedulingResetAt(account.Extra["grok_sched_reset_at"]),
},
}
// Official billing windows (weekly credits / monthly used%) from probe snapshot.
// Complements header-based grok_sched_* so paid SuperGrok accounts can auto-pause
// when request/token headers are sparse but billing % is high.
// Do not cross-fallback period ends (weekly until ≠ monthly end) to avoid multi-week parks.
const grokBillingThresholdStaleAfter = 48 * time.Hour
if billing, err := grokBillingSnapshotFromExtra(account.Extra); err == nil && billing != nil {
fetchedAt := firstNonEmpty(billing.UpdatedAt, billing.FetchedAt, billing.WeeklyUpdatedAt, billing.MonthlyUpdatedAt)
if !grokBillingSnapshotStale(fetchedAt, grokBillingThresholdStaleAfter) {
if billing.UsagePercent != nil {
if until := parseSchedulingResetAt(billing.PeriodEnd); until != nil {
candidates = append(candidates, &accountSchedulingThresholdCandidate{
window: "seven_day",
scope: "grok_billing",
usedPercent: *billing.UsagePercent,
until: until,
})
}
}
monthlyUtil := 0.0
hasMonthly := false
if billing.UsedPercent != nil {
monthlyUtil = *billing.UsedPercent
hasMonthly = true
} else if billing.MonthlyLimitCents != nil && *billing.MonthlyLimitCents > 0 && billing.UsedCents != nil {
monthlyUtil = (*billing.UsedCents / *billing.MonthlyLimitCents) * 100
hasMonthly = true
}
if hasMonthly {
if until := parseSchedulingResetAt(billing.BillingPeriodEnd); until != nil {
candidates = append(candidates, &accountSchedulingThresholdCandidate{
window: "thirty_day",
scope: "grok_billing",
usedPercent: monthlyUtil,
until: until,
})
}
}
}
}
return candidates
}
func grokBillingSnapshotStale(fetchedAtRFC3339 string, maxAge time.Duration) bool {
if maxAge <= 0 {
return false
}
raw := strings.TrimSpace(fetchedAtRFC3339)
if raw == "" {
// Unknown freshness: still allow (probe may omit timestamps on older snapshots).
return false
}
ts, err := time.Parse(time.RFC3339, raw)
if err != nil {
return false
}
return time.Since(ts) > maxAge
}
func pickLatestResetSchedulingCandidate(candidates []*accountSchedulingThresholdCandidate, threshold int, now time.Time) *accountSchedulingThresholdCandidate {
@@ -308,70 +308,32 @@ func TestEvaluateAccountSchedulingThreshold_GrokUsesConfiguredThresholds(t *test
require.True(t, wantUntil.Equal(*decision.Until))
}
func TestEvaluateAccountSchedulingThreshold_GrokBillingWindows(t *testing.T) {
func TestEvaluateAccountSchedulingThreshold_GrokUsesOnlyHeaderQuotaWindow(t *testing.T) {
t.Parallel()
now := time.Date(2026, 6, 3, 12, 0, 0, 0, time.UTC)
weeklyEnd := now.Add(3 * time.Hour)
weeklyPct := 95.0
monthlyPct := 40.0
account := &Account{
Platform: PlatformGrok,
Extra: map[string]any{
// Header-based quota below threshold.
"grok_sched_utilization": 50.0,
"grok_sched_reset_at": now.Add(time.Hour).Format(time.RFC3339),
// Official weekly billing above threshold (extra key used by probe persist).
grokBillingExtraKey: &xai.BillingSummary{
UsagePercent: &weeklyPct,
PeriodEnd: weeklyEnd.Format(time.RFC3339),
UsedPercent: &monthlyPct,
BillingPeriodEnd: now.Add(10 * 24 * time.Hour).Format(time.RFC3339),
},
},
}
decision := EvaluateAccountSchedulingThreshold(account, map[string]int{
PlatformGrok: 90,
}, now)
require.True(t, decision.ShouldPause)
require.Equal(t, "grok_billing", decision.Scope)
require.Equal(t, "seven_day", decision.Window)
require.InDelta(t, 95.0, decision.UsedPercent, 1e-9)
require.NotNil(t, decision.Until)
require.True(t, weeklyEnd.Equal(*decision.Until))
}
func TestEvaluateAccountSchedulingThreshold_GrokBillingStaleIgnored(t *testing.T) {
t.Parallel()
// personal-dev: billing seven_day/thirty_day must not drive pause; only grok_sched_*.
now := time.Date(2026, 6, 3, 12, 0, 0, 0, time.UTC)
weeklyEnd := now.Add(3 * time.Hour)
weeklyPct := 99.0
// Snapshot older than 48h → billing windows skipped; header util below threshold.
headerUntil := now.Add(2 * time.Hour)
account := &Account{
Platform: PlatformGrok,
Extra: map[string]any{
"grok_sched_utilization": 50.0,
"grok_sched_reset_at": now.Add(time.Hour).Format(time.RFC3339),
"grok_sched_utilization": 50.0, // below threshold
"grok_sched_reset_at": headerUntil.Format(time.RFC3339),
grokBillingExtraKey: &xai.BillingSummary{
UsagePercent: &weeklyPct,
PeriodEnd: weeklyEnd.Format(time.RFC3339),
UpdatedAt: now.Add(-49 * time.Hour).Format(time.RFC3339),
},
},
}
decision := EvaluateAccountSchedulingThreshold(account, map[string]int{PlatformGrok: 90}, now)
require.False(t, decision.ShouldPause, "high billing % alone must not pause under personal-dev windows")
decision := EvaluateAccountSchedulingThreshold(account, map[string]int{
PlatformGrok: 90,
}, now)
require.False(t, decision.ShouldPause)
}
func TestGrokBillingSnapshotStale(t *testing.T) {
t.Parallel()
require.False(t, grokBillingSnapshotStale("", 48*time.Hour))
require.False(t, grokBillingSnapshotStale(time.Now().UTC().Format(time.RFC3339), 48*time.Hour))
require.True(t, grokBillingSnapshotStale(time.Now().UTC().Add(-49*time.Hour).Format(time.RFC3339), 48*time.Hour))
account.Extra["grok_sched_utilization"] = 95.0
decision = EvaluateAccountSchedulingThreshold(account, map[string]int{PlatformGrok: 90}, now)
require.True(t, decision.ShouldPause)
require.Equal(t, "grok", decision.Scope)
require.Equal(t, "quota", decision.Window)
require.NotNil(t, decision.Until)
require.True(t, headerUntil.Equal(*decision.Until))
}
@@ -3,6 +3,7 @@ package service
import (
"context"
"log/slog"
"strings"
"sync"
"sync/atomic"
"time"
@@ -14,18 +15,18 @@ import (
// Local free-tier soft gate for Grok OAuth scheduling.
//
// Config keys (gateway.grok.*):
// - free_quota_soft_gate_enabled (bool, default true) — only applied when free-tier detection is strict
// - free_quota_token_limit (int64, default = xai free rolling limit 1_000_000)
// - free_quota_soft_gate_enabled (bool, default true)
// - free_quota_token_limit (int64, default 500_000)
// - free_quota_soft_gate_percent (int, default 95) — stop scheduling before the nominal limit
// - free_quota_window_hours (int, default 24) — local usage rolling window
// - free_quota_stats_cache_seconds (int, default 5) — bound hot-path aggregate query frequency
//
// Admin paths (QueryQuota / import probe / AccountUsageService.GetUsage) never call
// filterGrokFreeQuotaAccounts; only the OpenAI-compatible account scheduler filter does.
// Soft-gate applies only to *explicit* free OAuth (subscription_tier/plan_type ==
// "free"), matching personal-dev. Media/cache free detection uses isKnownGrokFreeAccount.
// Admin paths (QueryQuota / import probe) never call this filter.
const (
// Match current Free rolling limit (pkg/xai). A 2M default never pre-empts real exhaustion.
defaultGrokFreeQuotaTokenLimit int64 = 1_000_000
defaultGrokFreeQuotaTokenLimit int64 = 500_000
defaultGrokFreeQuotaSoftGatePercent = 95
defaultGrokFreeQuotaWindowHours = 24
)
@@ -85,11 +86,24 @@ func calculateGrokFreeQuotaSoftGateTokens(limit int64, percent int) int64 {
}
// isExplicitGrokFreeOAuthAccount decides whether the free soft-gate applies.
// Uses the same free/paid evidence as media eligibility (isKnownGrokFreeAccount):
// free/basic tiers, billing-inferred free, while paid plan/monthly evidence wins.
// Unknown/empty tier without free evidence and API-key accounts are not gated.
// personal-dev contract: only OAuth accounts with credentials/extra
// subscription_tier or plan_type exactly "free" (case-insensitive). Inferred
// free / basic / blank plan do not soft-gate.
func isExplicitGrokFreeOAuthAccount(account *Account) bool {
return isKnownGrokFreeAccount(account)
if account == nil || !account.IsGrokOAuth() {
return false
}
for _, tier := range []string{
account.GetCredential("subscription_tier"),
account.GetCredential("plan_type"),
account.GetExtraString("subscription_tier"),
account.GetExtraString("plan_type"),
} {
if strings.EqualFold(strings.TrimSpace(tier), "free") {
return true
}
}
return false
}
// filterGrokFreeQuotaAccounts applies a local, rolling soft gate only to
@@ -56,7 +56,7 @@ func (r *grokFreeQuotaUsageRepoStub) GetAccountWindowStatsBatch(_ context.Contex
func grokFreeQuotaTestConfig() *config.Config {
cfg := &config.Config{}
cfg.Gateway.Grok.FreeQuotaSoftGateEnabled = true
cfg.Gateway.Grok.FreeQuotaTokenLimit = 2_000_000
cfg.Gateway.Grok.FreeQuotaTokenLimit = 500_000
cfg.Gateway.Grok.FreeQuotaSoftGatePercent = 95
cfg.Gateway.Grok.FreeQuotaWindowHours = 24
cfg.Gateway.Grok.FreeQuotaStatsCacheSeconds = 5
@@ -65,7 +65,7 @@ func grokFreeQuotaTestConfig() *config.Config {
func TestFilterGrokFreeQuotaAccountsOnlyBlocksExplicitFreeOAuth(t *testing.T) {
repo := &grokFreeQuotaUsageRepoStub{stats: map[int64]*usagestats.AccountStats{
1: {Tokens: 1_900_000},
1: {Tokens: 475_000}, // 95% of 500k
}}
scheduler := &defaultOpenAIAccountScheduler{service: &OpenAIGatewayService{cfg: grokFreeQuotaTestConfig(), usageLogRepo: repo}}
accounts := []Account{
@@ -116,7 +116,7 @@ func TestFilterGrokFreeQuotaAccountsUnknownTierFailOpen(t *testing.T) {
func TestFilterGrokFreeQuotaAccountsRecoversAfterRollingUsageFalls(t *testing.T) {
repo := &grokFreeQuotaUsageRepoStub{stats: map[int64]*usagestats.AccountStats{
1: {Tokens: 1_950_000},
1: {Tokens: 490_000},
}}
scheduler := &defaultOpenAIAccountScheduler{service: &OpenAIGatewayService{cfg: grokFreeQuotaTestConfig(), usageLogRepo: repo}}
accounts := []Account{{
@@ -126,12 +126,12 @@ func TestFilterGrokFreeQuotaAccountsRecoversAfterRollingUsageFalls(t *testing.T)
require.Empty(t, scheduler.filterGrokFreeQuotaAccounts(context.Background(), accounts))
repo.mu.Lock()
repo.stats[1] = &usagestats.AccountStats{Tokens: 1_200_000}
repo.stats[1] = &usagestats.AccountStats{Tokens: 100_000}
repo.mu.Unlock()
require.Empty(t, scheduler.filterGrokFreeQuotaAccounts(context.Background(), accounts), "fresh cache keeps the short soft-gate hold")
scheduler.grokFreeQuotaGateCache.Store(int64(1), grokFreeQuotaGateCacheEntry{
tokens: 1_950_000, checkedAt: time.Now().Add(-time.Minute), known: true,
tokens: 490_000, checkedAt: time.Now().Add(-time.Minute), known: true,
})
require.Equal(t, []int64{1}, accountIDs(scheduler.filterGrokFreeQuotaAccounts(context.Background(), accounts)))
require.Equal(t, 2, repo.calls)
@@ -140,10 +140,22 @@ func TestFilterGrokFreeQuotaAccountsRecoversAfterRollingUsageFalls(t *testing.T)
func TestResolveGrokFreeQuotaGateSettingsDefaultsToNinetyFivePercent(t *testing.T) {
settings, ok := resolveGrokFreeQuotaGateSettings(grokFreeQuotaTestConfig())
require.True(t, ok)
require.Equal(t, int64(1_900_000), settings.gateTokens)
require.Equal(t, int64(500_000), settings.limitTokens)
require.Equal(t, int64(475_000), settings.gateTokens) // 95% of 500k
require.Equal(t, 24*time.Hour, settings.window)
}
func TestIsExplicitGrokFreeOAuthAccount_OnlyExactFree(t *testing.T) {
t.Parallel()
require.False(t, isExplicitGrokFreeOAuthAccount(nil))
require.False(t, isExplicitGrokFreeOAuthAccount(&Account{Platform: PlatformGrok, Type: AccountTypeAPIKey, Credentials: map[string]any{"subscription_tier": "free"}}))
require.True(t, isExplicitGrokFreeOAuthAccount(&Account{Platform: PlatformGrok, Type: AccountTypeOAuth, Credentials: map[string]any{"subscription_tier": "FREE"}}))
require.True(t, isExplicitGrokFreeOAuthAccount(&Account{Platform: PlatformGrok, Type: AccountTypeOAuth, Credentials: map[string]any{"plan_type": "free"}}))
// basic / inferred free are NOT soft-gated (personal-dev contract).
require.False(t, isExplicitGrokFreeOAuthAccount(&Account{Platform: PlatformGrok, Type: AccountTypeOAuth, Credentials: map[string]any{"subscription_tier": "basic"}}))
require.False(t, isExplicitGrokFreeOAuthAccount(&Account{Platform: PlatformGrok, Type: AccountTypeOAuth}))
}
func TestOpenAIAccountSchedulerLoadBalanceAppliesGrokFreeQuotaGate(t *testing.T) {
cfg := grokFreeQuotaTestConfig()
cfg.RunMode = config.RunModeSimple
@@ -155,7 +167,7 @@ func TestOpenAIAccountSchedulerLoadBalanceAppliesGrokFreeQuotaGate(t *testing.T)
cfg: cfg,
accountRepo: &grokFreeQuotaAccountRepoStub{accounts: accounts},
usageLogRepo: &grokFreeQuotaUsageRepoStub{stats: map[int64]*usagestats.AccountStats{
1: {Tokens: 1_900_000},
1: {Tokens: 480_000}, // over 95% of 500k
}},
}
scheduler := &defaultOpenAIAccountScheduler{service: svc, stats: newOpenAIAccountRuntimeStats()}
@@ -177,7 +189,7 @@ func TestGrokFreeQuotaGateIsSchedulerOnlyAdminPathUnfiltered(t *testing.T) {
require.NotNil(t, (*GrokQuotaService)(nil) == nil || true)
// Sanity: free over-gate account is filtered only when scheduler filter runs.
repo := &grokFreeQuotaUsageRepoStub{stats: map[int64]*usagestats.AccountStats{
9: {Tokens: 2_000_000},
9: {Tokens: 500_000},
}}
scheduler := &defaultOpenAIAccountScheduler{service: &OpenAIGatewayService{cfg: grokFreeQuotaTestConfig(), usageLogRepo: repo}}
overGate := Account{ID: 9, Platform: PlatformGrok, Type: AccountTypeOAuth, Credentials: map[string]any{"subscription_tier": "FREE"}}
@@ -304,6 +304,9 @@ func applyGrokFreeToolCacheRoute(body, intentSourceBody []byte, account *Account
return appendGrokFreeCacheNativeToolsWithPolicy(body, allowPureClientTools, allowFunctionSearch)
}
// isKnownGrokFreeAccount matches personal-dev free-tier recognition used for
// Free cache routing / media free_tier blocks (broader than soft-gate).
// Soft-gate uses isExplicitGrokFreeOAuthAccount (exact "free" only).
func isKnownGrokFreeAccount(account *Account) bool {
if account == nil || !account.IsGrokOAuth() {
return false
@@ -319,20 +322,15 @@ func isKnownGrokFreeAccount(account *Account) bool {
paidSignal = true
}
}
// Paid evidence: explicit plan name or monthly dollar cap — NOT mere presence of
// weekly/monthly usage percent (Free accounts also report creditUsagePercent).
if billing.MonthlyLimitCents != nil && *billing.MonthlyLimitCents > 0 {
// personal-dev: usage % or monthly dollar cap is paid evidence.
if billing.UsagePercent != nil || billing.UsedPercent != nil ||
(billing.MonthlyLimitCents != nil && *billing.MonthlyLimitCents > 0) {
paidSignal = true
}
// Infer free only from a successful *monthly* billing window with no paid
// plan/limit. Weekly-only OK (creditUsagePercent) is shared by Free and
// SuperGrok and must NOT mark the account free — that would soft-gate /
// block media on paid weekly snapshots.
monthlyOK := strings.TrimSpace(billing.MonthlyUpdatedAt) != "" ||
(billing.MonthlyStatusCode >= http.StatusOK && billing.MonthlyStatusCode < http.StatusMultipleChoices)
if monthlyOK && !billing.Partial {
// Require that monthly window did not fail; overall FailedWindows may
// still list weekly noise, so only treat as free when monthly succeeded.
// Empty plan + successful monthly observation → inferred free (no paid plan/limit).
if strings.TrimSpace(billing.MonthlyUpdatedAt) != "" ||
(billing.StatusCode >= http.StatusOK && billing.StatusCode < http.StatusMultipleChoices &&
!billing.Partial && len(billing.FailedWindows) == 0) {
inferredFreeSignal = true
}
}
@@ -349,34 +347,16 @@ func isKnownGrokFreeAccount(account *Account) bool {
inferredFreeSignal = true
}
}
for _, key := range []string{"subscription_tier", "plan_type"} {
if tier := strings.TrimSpace(account.GetCredential(key)); tier != "" {
if isGrokFreeSubscriptionTier(tier) {
freeSignal = true
} else if !isGrokUnknownSubscriptionTier(tier) {
paidSignal = true
}
}
if tier := strings.TrimSpace(account.GetExtraString(key)); tier != "" {
if isGrokFreeSubscriptionTier(tier) {
freeSignal = true
} else if !isGrokUnknownSubscriptionTier(tier) {
paidSignal = true
}
// personal-dev: only credentials subscription_tier (not plan_type / extra keys).
if tier := strings.TrimSpace(account.GetCredential("subscription_tier")); tier != "" {
if isGrokFreeSubscriptionTier(tier) {
freeSignal = true
} else if !isGrokUnknownSubscriptionTier(tier) {
paidSignal = true
}
}
// Strong paid evidence always wins — including over stale free credentials
// left after SuperGrok upgrade (subscription_tier still "free" until re-import).
if paidSignal {
return false
}
// Explicit free/basic tier (credentials or free plan string).
if freeSignal {
return true
}
// Inferred free (empty plan + successful monthly probe, or free token limit)
// only when there is no paid evidence.
return inferredFreeSignal
// Explicit paid evidence always wins over an inferred Free signal.
return !paidSignal && (freeSignal || inferredFreeSignal)
}
func isGrokFreeSubscriptionTier(tier string) bool {
@@ -90,7 +90,7 @@ func TestGetSchedulableAccount_AppliesGrokFreeSoftGate(t *testing.T) {
// Sticky/non-list path must not return over-gate free OAuth accounts.
cfg := &config.Config{}
cfg.Gateway.Grok.FreeQuotaSoftGateEnabled = true
cfg.Gateway.Grok.FreeQuotaTokenLimit = 1_000_000
cfg.Gateway.Grok.FreeQuotaTokenLimit = 500_000
cfg.Gateway.Grok.FreeQuotaSoftGatePercent = 95
cfg.Gateway.Grok.FreeQuotaWindowHours = 24
cfg.Gateway.Grok.FreeQuotaStatsCacheSeconds = 0
@@ -104,7 +104,7 @@ func TestGetSchedulableAccount_AppliesGrokFreeSoftGate(t *testing.T) {
accountsByID: map[int64]*Account{account.ID: account},
}
usageRepo := &grokFreeQuotaUsageRepoStub{stats: map[int64]*usagestats.AccountStats{
account.ID: {Tokens: 999_000},
account.ID: {Tokens: 480_000}, // above 95% of 500k
}}
// Clear shared gateway free-gate cache so this test is deterministic.
gatewayGrokFreeQuotaGateCache.Range(func(key, _ any) bool {
@@ -126,7 +126,7 @@ func TestOpenAIGetSchedulableAccount_AppliesGrokFreeSoftGate(t *testing.T) {
// Legacy OpenAI-compatible sticky (advanced scheduler off) must free-gate Grok.
cfg := &config.Config{}
cfg.Gateway.Grok.FreeQuotaSoftGateEnabled = true
cfg.Gateway.Grok.FreeQuotaTokenLimit = 1_000_000
cfg.Gateway.Grok.FreeQuotaTokenLimit = 500_000
cfg.Gateway.Grok.FreeQuotaSoftGatePercent = 95
cfg.Gateway.Grok.FreeQuotaWindowHours = 24
cfg.Gateway.Grok.FreeQuotaStatsCacheSeconds = 0
@@ -140,7 +140,7 @@ func TestOpenAIGetSchedulableAccount_AppliesGrokFreeSoftGate(t *testing.T) {
accountsByID: map[int64]*Account{account.ID: account},
}
usageRepo := &grokFreeQuotaUsageRepoStub{stats: map[int64]*usagestats.AccountStats{
account.ID: {Tokens: 999_000},
account.ID: {Tokens: 480_000},
}}
openaiGrokFreeQuotaGateCache.Range(func(key, _ any) bool {
openaiGrokFreeQuotaGateCache.Delete(key)