fix(ai): the operator cap counts only tokens the operator paid for

The reservation compared the day's TOTAL against whichever ceiling the
chain resolved, and the chain walker's hop guard did the same. On an
account whose background jobs run on the user's own ChatGPT plan, both
saw a total the operator had not paid for: the chat refused at 06:42Z
with 513 539 tokens against the 200 000 operator ceiling, and the
fallback hop to the shared key was closed on the same arithmetic.

An operator-funded chain is now measured against `operator_tokens` and a
user-plan chain keeps its abuse ceiling on the total; the hop guard reads
the operator share too. A day of turns the user's own plan paid for can
no longer lock anyone out of the operator's ceiling.
This commit is contained in:
Marc-André Bombeck committed 2026-09-11 09:56:44 +02:00
1 parent badef28e96
commit 2c46712e13
16 files changed
+285 -26

No files matched your search

@@ -39,7 +39,7 @@ vi.mock("@/lib/db", () => ({
// egress and reconciles after (`reserveBudget` / `reconcileSpend`), both
// over raw SQL. A zero prior total keeps every generation under the cap,
// so these suites keep testing what they were written to test.
$queryRaw: vi.fn(async () => [{ total_tokens: 0 }]),
$queryRaw: vi.fn(async () => [{ total_tokens: 0, operator_tokens: 0 }]),
$executeRaw: vi.fn(async () => 0),
user: {
findUnique: vi.fn(async () => ({
@@ -183,7 +183,7 @@ beforeEach(() => {
// `clearAllMocks` does not undo a `mockImplementation`, so the ledger stub is
// re-seeded per test — otherwise the over-cap case below leaks into the rest.
vi.mocked(prisma.$queryRaw).mockImplementation((async () => [
{ total_tokens: 0 },
{ total_tokens: 0, operator_tokens: 0 },
]) as never);
selfContextText.mockResolvedValue(null);
vi.mocked(checkRateLimit).mockResolvedValue({
@@ -254,8 +254,8 @@ describe("POST /api/insights/generate — daily token ceiling", () => {
strings: TemplateStringsArray,
) =>
String(strings[0]).includes("coach_usage")
? [{ total_tokens: 10_000_000 }]
: [{ total_tokens: 0 }]) as never);
? [{ total_tokens: 10_000_000, operator_tokens: 10_000_000 }]
: [{ total_tokens: 0, operator_tokens: 0 }]) as never);
makeWorkingProvider();
const res = await POST(jsonRequest({ force: true }) as never);