refactor(coach): collapse the duplicate daily-cap export to OPERATOR_COST_CAP

The operator-cost cap carried two export names for one value; keep the
accurate one (it gates only the operator-key path) and drop the alias.
This commit is contained in:
Marc-André Bombeck committed 2026-06-26 14:08:35 +02:00
1 parent e6ac604c83
commit 5f640e1420
4 files changed
+14 -19

No files matched your search

+6 -5
View File
@@ -3,7 +3,6 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import { HttpError } from "@/lib/api-handler";
import {
buildDateKey,
MAX_TOKENS_PER_USER_PER_DAY,
OPERATOR_COST_CAP,
USER_PLAN_CAP,
resolveDailyCap,
@@ -76,7 +75,7 @@ describe("budget", () => {
it("over-budget throws HttpError(429)", async () => {
prismaMock.coachUsage.findUnique.mockResolvedValue({
totalTokens: MAX_TOKENS_PER_USER_PER_DAY,
totalTokens: OPERATOR_COST_CAP,
});
const { enforceBudget } = await import("../budget");
await expect(enforceBudget("user-1", "2026-05-10")).rejects.toMatchObject({
@@ -233,11 +232,13 @@ describe("reconcileSpend cached-token subtraction (F3)", () => {
});
});
describe("MAX_TOKENS_PER_USER_PER_DAY (F2 — reasoning-aware operator cap)", () => {
describe("OPERATOR_COST_CAP (F2 — reasoning-aware operator cap)", () => {
it("is sized for reasoning turns, not a single non-reasoning reply", () => {
// Was 25_000 (≈ one gpt-5.x reasoning turn). Raised so the operator-key
// path survives a normal day of reasoning turns.
expect(MAX_TOKENS_PER_USER_PER_DAY).toBeGreaterThanOrEqual(150_000);
expect(OPERATOR_COST_CAP).toBe(MAX_TOKENS_PER_USER_PER_DAY);
expect(OPERATOR_COST_CAP).toBeGreaterThanOrEqual(150_000);
// The user-plan cap is far more generous — a user's own egress is never
// gated on the operator-cost ceiling.
expect(USER_PLAN_CAP).toBeGreaterThan(OPERATOR_COST_CAP);
});
});
+3 -9
View File
@@ -34,13 +34,7 @@ import type { ProviderChainType } from "@/lib/ai/provider-chain";
* surgery) while leaving room for a normal day of reasoning turns on the
* operator's key. The cap stays a real ceiling on the operator's exposure.
*/
export const MAX_TOKENS_PER_USER_PER_DAY = 200_000;
/**
* v1.21.0 (F1) — historical name retained for any external reference; the
* operator-cost cap is the only cap the ledger enforces.
*/
export const OPERATOR_COST_CAP = MAX_TOKENS_PER_USER_PER_DAY;
export const OPERATOR_COST_CAP = 200_000;
/**
* v1.21.0 (F1) — the daily ceiling for a chain whose egress runs on the
@@ -108,7 +102,7 @@ export async function getDailyTokenSpend(
export async function enforceBudget(
userId: string,
dateKey: string = buildDateKey(),
cap: number = MAX_TOKENS_PER_USER_PER_DAY,
cap: number = OPERATOR_COST_CAP,
): Promise<void> {
const spent = await getDailyTokenSpend(userId, dateKey);
if (spent >= cap) {
@@ -148,7 +142,7 @@ export async function reserveBudget(
userId: string,
estimatedTokens: number,
dateKey: string = buildDateKey(),
cap: number = MAX_TOKENS_PER_USER_PER_DAY,
cap: number = OPERATOR_COST_CAP,
): Promise<ReserveBudgetResult> {
const reserved =
Number.isFinite(estimatedTokens) && estimatedTokens > 0
+2 -2
View File
@@ -26,7 +26,7 @@ import { singleUserTurn } from "@/lib/ai/types";
import {
buildDateKey,
getDailyTokenSpend,
MAX_TOKENS_PER_USER_PER_DAY,
OPERATOR_COST_CAP,
recordSpend,
} from "@/lib/ai/coach/budget";
import {
@@ -137,7 +137,7 @@ export async function deriveClarifyingQuestions(
// Budget gate — same per-day ledger the Coach chat enforces.
const dateKey = buildDateKey();
const spent = await getDailyTokenSpend(userId, dateKey);
if (spent >= MAX_TOKENS_PER_USER_PER_DAY) return fallback();
if (spent >= OPERATOR_COST_CAP) return fallback();
const provider = await resolveProvider(userId);
// v1.16.6 — same snapshot the Coach chat rides; lets the model
+3 -3
View File
@@ -24,7 +24,7 @@ process.env.ENCRYPTION_KEY ??=
import { cookieJar, headerJar } from "./mock-next-headers";
import { getPrismaClient, truncateAllTables } from "./setup";
import { MAX_TOKENS_PER_USER_PER_DAY } from "@/lib/ai/coach/budget";
import { OPERATOR_COST_CAP } from "@/lib/ai/coach/budget";
vi.mock("next/headers", async () => {
const { cookieJar, headerJar } = await import("./mock-next-headers");
@@ -227,7 +227,7 @@ describe("POST /api/insights/chat — integration", () => {
data: {
userId,
dateKey,
totalTokens: MAX_TOKENS_PER_USER_PER_DAY,
totalTokens: OPERATOR_COST_CAP,
messageCount: 50,
},
});
@@ -251,7 +251,7 @@ describe("POST /api/insights/chat — integration", () => {
const usage = await prisma.coachUsage.findUnique({
where: { userId_dateKey: { userId, dateKey } },
});
expect(usage?.totalTokens).toBe(MAX_TOKENS_PER_USER_PER_DAY);
expect(usage?.totalTokens).toBe(OPERATOR_COST_CAP);
});
it("returns 404 (not 403) when conversationId belongs to another user", async () => {