fix(quotas): unifier le décompte IA (BYOK, rollback) et combler les fuites
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 6m55s
CI / Deploy production (on server) (push) Successful in 36s

Centralise la réserve via ai-quota, corrige admin unavailable (-1), brancher les routes sans quota et le host-pays brainstorm, avec usage-meter élargi, noms de clusters, MCP et ajustements dashboard/insights.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Antigravity
2026-07-15 20:42:25 +00:00
parent 30da592ba2
commit 4fe31ebc99
75 changed files with 2949 additions and 785 deletions

View File

@@ -190,6 +190,46 @@ local newCount = tonumber(redis.call('GET', KEYS[1]))
return newCount
`;
const RELEASE_LUA = `
local current = tonumber(redis.call('GET', KEYS[1]) or '0')
if current <= 0 then return 0 end
redis.call('DECR', KEYS[1])
return tonumber(redis.call('GET', KEYS[1]) or '0')
`;
export async function releaseUsage(userId: string, feature: string): Promise<void> {
if (!isValidFeature(feature)) return;
const tier = await getEffectiveTier(userId);
const limit = await getLimitAsync(tier, feature);
if (limit === undefined || limit === Infinity) return;
try {
const key = getRedisKey(userId, feature);
const raw = await redis.eval(RELEASE_LUA, 1, key);
const newCount = parseReserveResult(raw);
if (Number.isFinite(newCount) && newCount >= 0) {
await mirrorUsageCountToDatabase(userId, feature, newCount);
}
} catch (err) {
console.error('[entitlements] Redis release failed, trying DB fallback:', err);
try {
const { periodStart } = getPeriodDates();
const existing = await prisma.usageLog.findUnique({
where: { userId_feature_periodStart: { userId, feature, periodStart } },
});
const current = existing?.requestsCount ?? 0;
if (current <= 0) return;
await prisma.usageLog.update({
where: { userId_feature_periodStart: { userId, feature, periodStart } },
data: { requestsCount: current - 1, syncedAt: new Date() },
});
} catch (dbErr) {
console.error('[entitlements] DB release fallback failed:', dbErr);
}
}
}
const RESERVE_LUA = `
local limit = tonumber(ARGV[1])
local ttl = tonumber(ARGV[2])
@@ -472,6 +512,9 @@ export async function getUserQuotas(
return result;
} catch (err) {
console.error('[entitlements] getUserQuotas Redis error:', err);
if (shouldFailClosedOnRedisError()) {
throw new QuotaServiceUnavailableError();
}
const result: Record<string, { remaining: number; limit: number; used: number }> = {};
for (const feature of features) {
const limit = (await getLimitAsync(tier, feature)) ?? 0;