diff --git a/prisma/migrations/0340_coach_usage_operator_tokens/migration.sql b/prisma/migrations/0340_coach_usage_operator_tokens/migration.sql index 8414906ba..2ad77f48f 100644 --- a/prisma/migrations/0340_coach_usage_operator_tokens/migration.sql +++ b/prisma/migrations/0340_coach_usage_operator_tokens/migration.sql @@ -1,4 +1,4 @@ --- v1.38.19 (Wave E) — the daily coach ledger gains a cost-owner dimension. +-- v1.38.19 — the daily coach ledger gains a cost-owner dimension. -- -- `coach_usage` counted one number per (user, day), so tokens the USER's own -- plan paid for (a `codex` fallback hop after the operator's shared key diff --git a/prisma/migrations/0341_arrival_reaction_cost_owner/migration.sql b/prisma/migrations/0341_arrival_reaction_cost_owner/migration.sql index 74fee8a02..7cc71a13b 100644 --- a/prisma/migrations/0341_arrival_reaction_cost_owner/migration.sql +++ b/prisma/migrations/0341_arrival_reaction_cost_owner/migration.sql @@ -1,4 +1,4 @@ --- v1.38.19 (Wave E, fix round 1) — the arrival reaction remembers who paid for +-- v1.38.19 — the arrival reaction remembers who paid for -- its reservation. -- -- The row persisted the reserved amount and the date key but not the cost diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0a2fe73c0..09d54ef02 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -6271,7 +6271,7 @@ model CoachUsage { userId String @map("user_id") dateKey String @map("date_key") totalTokens Int @default(0) @map("total_tokens") - /// v1.38.19 (Wave E) — the share of `totalTokens` served by a provider the + /// v1.38.19 — the share of `totalTokens` served by a provider the /// OPERATOR funds (`admin-openai` / `admin-codex`). The operator-cost cap is /// enforced against this counter and the user-plan cap against /// `totalTokens`, so background work served by the user's own plan can no @@ -6761,7 +6761,7 @@ model ArrivalReaction { /// `coach_usage` increment. A reclaimed pre-provider attempt reuses it. generationReservedTokens Int? @map("generation_reserved_tokens") generationBudgetDateKey String? @map("generation_budget_date_key") - /// v1.38.19 (Wave E) — who the reservation above was booked to, `"operator"` + /// v1.38.19 — who the reservation above was booked to, `"operator"` /// or `"user"`. Persisted rather than recomputed: the supersede refund in /// `data-arrival.ts` has no chain to ask, and the resume path's chain may /// have been reordered by the health ledger since the reservation was made. diff --git a/src/__tests__/coach-usage-owner-columns-guard.test.ts b/src/__tests__/coach-usage-owner-columns-guard.test.ts index bda895bfe..7b12a1a95 100644 --- a/src/__tests__/coach-usage-owner-columns-guard.test.ts +++ b/src/__tests__/coach-usage-owner-columns-guard.test.ts @@ -2,7 +2,7 @@ * Structural guard — `coach_usage.total_tokens` and `coach_usage.operator_tokens` * move together in EVERY raw statement. * - * v1.38.19 (Wave E) gave the daily ledger a second counter: `operator_tokens` + * v1.38.19 gave the daily ledger a second counter: `operator_tokens` * is the share of `total_tokens` an operator-funded provider served, and the * operator ceiling is enforced against it. The counters are only meaningful * while they stay in step. A statement that writes the total and forgets the @@ -10,7 +10,8 @@ * reverses the total and forgets the operator share leaks budget permanently * toward the operator cap, because nothing else ever subtracts it; a statement * that RETURNS the total alone forces its caller to gate on the wrong number, - * which is the exact defect the wave exists to fix. + * which is how an operator-funded chain ends up refused over tokens the user's + * own plan paid for. * * Two of the three writers shipped with precisely those bugs, and both were a * grep away. This guard is that grep, executable: every INSERT column list, diff --git a/src/app/api/admin/provider-health/__tests__/route.test.ts b/src/app/api/admin/provider-health/__tests__/route.test.ts index f0f819c3f..408ba63ea 100644 --- a/src/app/api/admin/provider-health/__tests__/route.test.ts +++ b/src/app/api/admin/provider-health/__tests__/route.test.ts @@ -207,7 +207,7 @@ describe("GET /api/admin/provider-health", () => { }); /** - * v1.38.19 (Wave E) — the two spend figures side by side. + * v1.38.19 — the two spend figures side by side. * * The operator reads this card when the chat refuses. Until now it showed * delivery health only, and the day's token figure was one number that mixed diff --git a/src/app/api/admin/provider-health/route.ts b/src/app/api/admin/provider-health/route.ts index 0e8b42e13..78e90ca01 100644 --- a/src/app/api/admin/provider-health/route.ts +++ b/src/app/api/admin/provider-health/route.ts @@ -111,7 +111,7 @@ export const GET = apiHandler(async () => { return a.providerType.localeCompare(b.providerType); }); - // v1.38.19 (Wave E) — the day's spend, split by who pays for it. + // v1.38.19 — the day's spend, split by who pays for it. // // The operator opens this card when a surface refuses on budget. One mixed // number could not answer the question it raised: a day at 1.24 M tokens is diff --git a/src/app/api/insights/chat/route.ts b/src/app/api/insights/chat/route.ts index c0880ce9d..c2e6315e4 100644 --- a/src/app/api/insights/chat/route.ts +++ b/src/app/api/insights/chat/route.ts @@ -609,7 +609,7 @@ async function handleChatRequest(request: NextRequest): Promise { "coach", ); if (!reservation.allowed) { - // v1.38.19 (Wave E, fix round 1) — say WHICH ceiling refused and WHOSE. + // v1.38.19 — say WHICH ceiling refused and WHOSE. // `totalAfter` alone is the day's mixed total; on an operator refusal that // is not the counter that tripped, and reading `totalAfter: 1200000` // against a 200 000 ceiling is what turned the 06:42Z incident into a @@ -654,7 +654,7 @@ async function handleChatRequest(request: NextRequest): Promise { async function produceReply(): Promise { let result: CompletionResult; - // v1.38.19 (Wave E) — typed as the chain's provider union, not a bare + // v1.38.19 — typed as the chain's provider union, not a bare // string: the budget reconcile attributes the turn's tokens to the cost // owner of the hop named here. let workingProviderType: ProviderChainType; diff --git a/src/app/api/labs/ocr/extract/__tests__/route.test.ts b/src/app/api/labs/ocr/extract/__tests__/route.test.ts index 9471f6ac1..97b655c31 100644 --- a/src/app/api/labs/ocr/extract/__tests__/route.test.ts +++ b/src/app/api/labs/ocr/extract/__tests__/route.test.ts @@ -120,9 +120,9 @@ describe("POST /api/labs/ocr/extract — text mode budget", () => { "2026-06-26", // F1 — the provider-aware daily cap (mocked) is threaded as the 4th arg. 200_000, - // Wave E — and the cost owner that cap is enforced against as the 5th. + // ...the cost owner that cap is enforced against as the 5th... "operator", - // Wave E fix round 1 — the surface the ceiling is rationed for, 6th. + // ...and the surface the ceiling is rationed for as the 6th. "coach", ); }); diff --git a/src/components/admin/__tests__/provider-health-spend-today.test.tsx b/src/components/admin/__tests__/provider-health-spend-today.test.tsx index 1f92bdc03..bfe056560 100644 --- a/src/components/admin/__tests__/provider-health-spend-today.test.tsx +++ b/src/components/admin/__tests__/provider-health-spend-today.test.tsx @@ -4,7 +4,7 @@ import { renderToStaticMarkup } from "react-dom/server"; import { I18nProvider } from "@/lib/i18n/context"; /** - * v1.38.19 (Wave E) — the day's spend, split by who paid for it. + * v1.38.19 — the day's spend, split by who paid for it. * * `/api/admin/provider-health` answers `spendToday` and nothing rendered it, * which is the "the reader is the follow-up" shape the two-ended rule exists to diff --git a/src/components/admin/provider-health-section.tsx b/src/components/admin/provider-health-section.tsx index fecc95556..273c3559b 100644 --- a/src/components/admin/provider-health-section.tsx +++ b/src/components/admin/provider-health-section.tsx @@ -27,7 +27,7 @@ import { formatDateTime } from "@/lib/format"; */ /** - * v1.38.19 (Wave E) — the day's spend, split by who pays for it. The operator + * v1.38.19 — the day's spend, split by who pays for it. The operator * opens this card when a surface refuses on budget, and one mixed number could * not answer the question that raises: a day at 1.24 M tokens is alarming until * you see that 151 200 of them were the instance's own key and the rest ran on diff --git a/src/lib/ai/__tests__/provider-runner.test.ts b/src/lib/ai/__tests__/provider-runner.test.ts index 3ff5d781a..0080fc64b 100644 --- a/src/lib/ai/__tests__/provider-runner.test.ts +++ b/src/lib/ai/__tests__/provider-runner.test.ts @@ -774,7 +774,7 @@ describe("runRawCompletionWithFallback — operator-cost cap at hop time", () => expect(adminOpenai.callCount).toBe(1); }); - // v1.38.19 (Wave E) — the guard reads the OPERATOR-funded share. + // v1.38.19 — the guard reads the OPERATOR-funded share. // // Production evidence (2026-09-11): the operator's day held ~1.2 M tokens, // nearly all served by `codex` on his own ChatGPT plan. Comparing the day's @@ -833,7 +833,7 @@ describe("runRawCompletionWithFallback — operator-cost cap at hop time", () => expect(adminOpenai.callCount).toBe(0); }); - // v1.38.19 (Wave E, fix round 1) — the hop guard knows which SURFACE asked. + // v1.38.19 — the hop guard knows which SURFACE asked. // // Without this the job share was enforced at reservation time only: a // background reservation admitted under `resolveDailyCapFor("job", …)` could diff --git a/src/lib/ai/coach/__tests__/budget.test.ts b/src/lib/ai/coach/__tests__/budget.test.ts index 2a7f764b4..e303151d1 100644 --- a/src/lib/ai/coach/__tests__/budget.test.ts +++ b/src/lib/ai/coach/__tests__/budget.test.ts @@ -198,7 +198,7 @@ describe("reserveBudget cap (F1 — user-plan path not locked out)", () => { }); /** - * v1.38.19 (Wave E) — the cap is enforced against the counter it is ABOUT. + * v1.38.19 — the cap is enforced against the counter it is ABOUT. * * Production evidence (2026-09-11): the operator's day held 1.0–1.45 M tokens, * almost all served by `codex` on his own ChatGPT plan after the shared @@ -207,14 +207,14 @@ describe("reserveBudget cap (F1 — user-plan path not locked out)", () => { * is the defect; the operator ceiling may only see operator-funded tokens. */ /** - * v1.38.19 (Wave E) — background generation gets a bounded slice of the day. + * v1.38.19 — background generation gets a bounded slice of the day. * * Production evidence (2026-09-11): 150–330 ledger rows a day on the * operator's account, 172 `insights.metric` generations by 06:42Z — the * automatic jobs had spent the day's ceiling before he opened the chat. A * background surface may reserve at most half of the interactive ceiling. */ -describe("resolveDailyCapFor (Wave E — the job share)", () => { +describe("resolveDailyCapFor — the job share", () => { it("gives a background surface half the operator ceiling", () => { expect(resolveDailyCapFor("job", [{ providerType: "admin-openai" }])).toBe( OPERATOR_COST_CAP * JOB_SURFACE_SHARE, @@ -247,7 +247,7 @@ describe("resolveDailyCapFor (Wave E — the job share)", () => { }); }); -describe("resolveTotalCapFor (Wave E, fix round 1 — the abuse ceiling)", () => { +describe("resolveTotalCapFor — the abuse ceiling", () => { it("keeps the user-plan ceiling on the day's mixed total for both owners", () => { // The operator arm compares `operator_tokens`, a counter that returns to // ~0 on every reconcile the user's own plan settled. Without a ceiling on @@ -267,7 +267,7 @@ describe("resolveTotalCapFor (Wave E, fix round 1 — the abuse ceiling)", () => }); }); -describe("reserveBudget cap by cost owner (Wave E)", () => { +describe("reserveBudget — the cap follows the cost owner", () => { let prismaMock: { $queryRaw: ReturnType; $executeRaw: ReturnType; @@ -336,7 +336,7 @@ describe("reserveBudget cap by cost owner (Wave E)", () => { expect(res.allowed).toBe(false); }); - // v1.38.19 (Wave E, fix round 1) — the abuse ceiling survives the owner split. + // v1.38.19 — the abuse ceiling survives the owner split. // // The first cut compared `operator_tokens` and NOTHING else for an // operator-funded chain. On the 2026-09-11 shape that counter never grows: diff --git a/src/lib/ai/coach/budget.ts b/src/lib/ai/coach/budget.ts index e2b77969c..ec01876ea 100644 --- a/src/lib/ai/coach/budget.ts +++ b/src/lib/ai/coach/budget.ts @@ -18,7 +18,7 @@ import { prisma } from "@/lib/db"; import type { ProviderChainType } from "@/lib/ai/provider-chain"; /** - * v1.38.19 (Wave E, fix round 1) — the two raw statements the ledger owns can + * v1.38.19 — the two raw statements the ledger owns can * run on the singleton client OR inside an open transaction. The arrival * reaction reserves its tokens in the same transaction that links the * reservation to the claimed marker, and used to carry its own copy of the @@ -79,7 +79,7 @@ export function resolveDailyCap( } /** - * v1.38.19 (Wave E) — the share of a day's ceiling that BACKGROUND generation + * v1.38.19 — the share of a day's ceiling that BACKGROUND generation * may reserve, all job surfaces together. * * Production evidence (2026-09-11): the operator's account wrote 150–330 @@ -91,7 +91,7 @@ export function resolveDailyCap( export const JOB_SURFACE_SHARE = 0.5; /** - * v1.38.19 (Wave E) — which kind of caller is asking. `"coach"` is every + * v1.38.19 — which kind of caller is asking. `"coach"` is every * interactive surface (the chat, the extraction routes, the document routes, * the connection probe): a person is waiting. `"job"` is the automatic * generators, which run with nobody waiting and must leave the interactive @@ -100,7 +100,7 @@ export const JOB_SURFACE_SHARE = 0.5; export type BudgetSurface = "coach" | "job"; /** - * v1.38.19 (Wave E, fix round 1) — the job share rations the OPERATOR's + * v1.38.19 — the job share rations the OPERATOR's * invoice, so it applies only when the operator is paying. * * The first cut multiplied whichever ceiling `resolveDailyCap` returned, which @@ -120,7 +120,7 @@ function applyJobShare( } /** - * v1.38.19 (Wave E) — the daily ceiling for one SURFACE on a chain. `"coach"` + * v1.38.19 — the daily ceiling for one SURFACE on a chain. `"coach"` * (every interactive surface: the chat, the extraction routes, the document * routes, the connection probe) gets the whole ceiling; `"job"` (the automatic * generators) gets `JOB_SURFACE_SHARE` of it. The cost owner is unchanged — @@ -135,7 +135,7 @@ export function resolveDailyCapFor( } /** - * v1.38.19 (Wave E, fix round 1) — the ABUSE ceiling on the day's mixed total, + * v1.38.19 — the ABUSE ceiling on the day's mixed total, * for one surface and one cost owner. * * `resolveDailyCapFor` answers "how much of the owner's ceiling may this @@ -169,14 +169,14 @@ export function isOperatorFundedProvider(type: ProviderChainType): boolean { } /** - * v1.38.19 (Wave E) — who pays for a turn. `"operator"` means the tokens land + * v1.38.19 — who pays for a turn. `"operator"` means the tokens land * on the operator's invoice (`admin-openai` / `admin-codex`), `"user"` means * the user's own plan, key or hardware carries them. */ export type BudgetCostOwner = "operator" | "user"; /** - * v1.38.19 (Wave E) — the cost owner a chain RESERVES under: the primary is + * v1.38.19 — the cost owner a chain RESERVES under: the primary is * the provider that will be tried first, so it is the expected payer. An empty * chain defaults to the operator — the conservative side. `resolveDailyCap` * and `reserveBudget` must agree on this classification, so both read it here. @@ -204,7 +204,7 @@ export interface DailySpend { /** Every token recorded for the day, whoever paid for it. */ total: number; /** - * v1.38.19 (Wave E) — the share of `total` served by an operator-funded + * v1.38.19 — the share of `total` served by an operator-funded * provider. The hop guard compares THIS against `OPERATOR_COST_CAP`: a day * full of turns the user's own plan paid for must not close the operator's * fallback hop. @@ -264,7 +264,7 @@ export interface ReserveBudgetResult { /** The day's total AFTER this reservation (for observability). */ totalAfter: number; /** - * v1.38.19 (Wave E) — the owner this reservation was charged to. The + * v1.38.19 — the owner this reservation was charged to. The * reconcile needs it: only a reservation booked to the operator has an * amount to move back OUT of `operator_tokens` when a user-funded hop ends * up serving the turn. @@ -273,7 +273,7 @@ export interface ReserveBudgetResult { /** The day's operator-funded spend AFTER this reservation. */ operatorAfter: number; /** - * v1.38.19 (Wave E, fix round 1) — which ceiling refused this reservation, + * v1.38.19 — which ceiling refused this reservation, * `null` when it was admitted. `"owner-cap"` is the cost owner's daily * ceiling (the operator's invoice, or the user's own plan); `"total-cap"` is * the abuse ceiling on the day's mixed total. The refusal annotations carry @@ -298,7 +298,7 @@ export async function reserveBudget( Number.isFinite(estimatedTokens) && estimatedTokens > 0 ? Math.floor(estimatedTokens) : 0; - // v1.38.19 (Wave E) — a reservation is booked to `operator_tokens` only when + // v1.38.19 — a reservation is booked to `operator_tokens` only when // the chain's primary is operator-funded. The counter moves inside the SAME // statement as the total, so two concurrent requests can never observe one // counter without the other. @@ -328,14 +328,14 @@ export async function reserveBudget( // PRIOR to its reservation was under the cap. So compare the post-increment // figure minus this reservation against the cap. // - // v1.38.19 (Wave E) — and compare the counter the cap is ABOUT. An + // v1.38.19 — and compare the counter the cap is ABOUT. An // operator-funded chain is measured against the day's operator-funded spend, // never against a total that the user's own plan inflated; a user-plan chain // keeps its abuse ceiling on the total. const priorTotal = totalAfter - reserved; const priorOperator = operatorAfter - operatorReserved; const prior = owner === "operator" ? priorOperator : priorTotal; - // v1.38.19 (Wave E, fix round 1) — BOTH ceilings, always. The owner's cap + // v1.38.19 — BOTH ceilings, always. The owner's cap // protects whoever pays; the total cap is the abuse ceiling that keeps a // runaway loop — or a day of background generation the operator's key never // served — from writing an unbounded row. Dropping the second one for @@ -412,7 +412,7 @@ export async function reconcileSpend( // (shouldn't happen, but the wire is untrusted) can't drive a negative charge. const actual = Math.max(0, grossActual - cached); const delta = actual - reserved; - // v1.38.19 (Wave E) — settle the operator's counter against the hop that + // v1.38.19 — settle the operator's counter against the hop that // SERVED, not the one the chain expected. Book the actual tokens when an // operator-funded provider answered, and take back whatever this // reservation had put there (nothing, when the reservation was the user's diff --git a/src/lib/ai/provider-runner.ts b/src/lib/ai/provider-runner.ts index dd2dfec4b..433821bd5 100644 --- a/src/lib/ai/provider-runner.ts +++ b/src/lib/ai/provider-runner.ts @@ -438,11 +438,11 @@ async function runRawChain( total: 0, operator: 0, })); - // v1.38.19 (Wave E) — the OPERATOR-funded share, not the day's total: a + // v1.38.19 — the OPERATOR-funded share, not the day's total: a // day filled by the user's own plan must not close the operator's // fallback hop on money the operator never spent. // - // v1.38.19 (Wave E, fix round 1) — and at the SURFACE's ceiling, not the + // v1.38.19 — and at the SURFACE's ceiling, not the // full one. A background reservation is rationed at the job share when it // is taken; without this the hop it reaches by fallback could still spend // against the whole operator cap, which is how the original defect stayed @@ -540,7 +540,7 @@ export async function runStreamingRawCompletionWithFallback(args: { providers: ProviderChainResolved[]; params: CompletionParams; /** - * v1.38.19 (Wave E, fix round 1) — which ceiling an operator-funded FALLBACK + * v1.38.19 — which ceiling an operator-funded FALLBACK * hop is rationed against. `"job"` gets the background share, exactly as the * reservation did; `"coach"` gets the whole ceiling. */ diff --git a/src/lib/documents/auto-stage-labs.ts b/src/lib/documents/auto-stage-labs.ts index d77c9391f..23478147f 100644 --- a/src/lib/documents/auto-stage-labs.ts +++ b/src/lib/documents/auto-stage-labs.ts @@ -278,7 +278,7 @@ export async function maybeAutoStageLabFacts( // A text-structuring pass, not a vision call — reserve the proportionate // ceiling under the cost owner the auto-index resolved. - // v1.38.19 (Wave E) — auto-staging runs off the upload, with no one waiting + // v1.38.19 — auto-staging runs off the upload, with no one waiting // on it: a background surface, so half the day's ceiling. const dateKey = buildDateKey(); const reservation = await reserveBudget( diff --git a/src/lib/documents/index-document.ts b/src/lib/documents/index-document.ts index 76b0a28a3..e512aee6e 100644 --- a/src/lib/documents/index-document.ts +++ b/src/lib/documents/index-document.ts @@ -131,7 +131,7 @@ export interface ResolvedIndexProvider { consentOk: boolean; dailyCap: number; /** - * v1.38.19 (Wave E) — whose budget `dailyCap` rations. The reservation books + * v1.38.19 — whose budget `dailyCap` rations. The reservation books * `operator_tokens` only for an operator-funded pick, so the cap and the * counter it is compared against are resolved from one place. */ @@ -168,7 +168,7 @@ export async function resolveIndexProvider( pick && consentOk ? resolveDailyCap([{ providerType: pick.entry.providerType }]) : 0; - // v1.38.19 (Wave E) — the cap and its cost owner are resolved from the SAME + // v1.38.19 — the cap and its cost owner are resolved from the SAME // pick, so the reservation books the counter the cap is enforced against. const costOwner: BudgetCostOwner = pick && consentOk diff --git a/src/lib/insights/__tests__/briefing-provider-budget.test.ts b/src/lib/insights/__tests__/briefing-provider-budget.test.ts index b51df1541..9f578cb4d 100644 --- a/src/lib/insights/__tests__/briefing-provider-budget.test.ts +++ b/src/lib/insights/__tests__/briefing-provider-budget.test.ts @@ -21,7 +21,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; /** In-memory stand-in for the day's `coach_usage.total_tokens`. */ let ledgerTotal = 0; -/** …and of `coach_usage.operator_tokens` (Wave E). */ +/** …and of `coach_usage.operator_tokens`. */ let ledgerOperator = 0; vi.mock("@/lib/db", () => ({ @@ -30,7 +30,7 @@ vi.mock("@/lib/db", () => ({ // (strings, userId, dateKey, reserved, reserved). $queryRaw: vi.fn( async (_strings: TemplateStringsArray, ...v: unknown[]) => { - // Wave E — (userId, dateKey, reserved, operatorReserved, …). + // (userId, dateKey, reserved, operatorReserved, …). ledgerTotal += Number(v[2] ?? 0); ledgerOperator += Number(v[3] ?? 0); return [{ total_tokens: ledgerTotal, operator_tokens: ledgerOperator }]; @@ -41,7 +41,7 @@ vi.mock("@/lib/db", () => ({ async (strings: TemplateStringsArray, ...v: unknown[]) => { const sql = strings.join("?"); const amount = Number(v[0] ?? 0); - // Wave E — the operator-funded share moves in the same statement. + // The operator-funded share moves in the same statement. const operatorAmount = Number(v[1] ?? 0); if (sql.includes("total_tokens + ")) { ledgerTotal += amount; diff --git a/src/lib/insights/__tests__/comprehensive-budget-refusal.test.ts b/src/lib/insights/__tests__/comprehensive-budget-refusal.test.ts index 8d214b0b6..b000afac4 100644 --- a/src/lib/insights/__tests__/comprehensive-budget-refusal.test.ts +++ b/src/lib/insights/__tests__/comprehensive-budget-refusal.test.ts @@ -15,7 +15,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; let ledgerTotal = 0; -/** …and of `coach_usage.operator_tokens` (Wave E). */ +/** …and of `coach_usage.operator_tokens`. */ let ledgerOperator = 0; const findUnique = vi.fn(); @@ -31,7 +31,7 @@ vi.mock("@/lib/db", () => ({ prisma: { $queryRaw: vi.fn( async (_strings: TemplateStringsArray, ...v: unknown[]) => { - // Wave E — (userId, dateKey, reserved, operatorReserved, …). + // (userId, dateKey, reserved, operatorReserved, …). ledgerTotal += Number(v[2] ?? 0); ledgerOperator += Number(v[3] ?? 0); return [{ total_tokens: ledgerTotal, operator_tokens: ledgerOperator }]; @@ -41,7 +41,7 @@ vi.mock("@/lib/db", () => ({ async (strings: TemplateStringsArray, ...v: unknown[]) => { const sql = strings.join("?"); const amount = Number(v[0] ?? 0); - // Wave E — the operator-funded share moves in the same statement. + // The operator-funded share moves in the same statement. const operatorAmount = Number(v[1] ?? 0); if (sql.includes("total_tokens + ")) { ledgerTotal += amount; diff --git a/src/lib/insights/__tests__/status-provider-budget.test.ts b/src/lib/insights/__tests__/status-provider-budget.test.ts index c756c3dac..c22616a04 100644 --- a/src/lib/insights/__tests__/status-provider-budget.test.ts +++ b/src/lib/insights/__tests__/status-provider-budget.test.ts @@ -18,7 +18,7 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; /** In-memory stand-in for the day's `coach_usage.total_tokens`. */ let ledgerTotal = 0; -/** …and of `coach_usage.operator_tokens` (Wave E). */ +/** …and of `coach_usage.operator_tokens`. */ let ledgerOperator = 0; vi.mock("@/lib/db", () => ({ @@ -27,7 +27,7 @@ vi.mock("@/lib/db", () => ({ // (strings, userId, dateKey, reserved, reserved). $queryRaw: vi.fn( async (_strings: TemplateStringsArray, ...v: unknown[]) => { - // Wave E — (userId, dateKey, reserved, operatorReserved, …). + // (userId, dateKey, reserved, operatorReserved, …). ledgerTotal += Number(v[2] ?? 0); ledgerOperator += Number(v[3] ?? 0); return [{ total_tokens: ledgerTotal, operator_tokens: ledgerOperator }]; @@ -38,7 +38,7 @@ vi.mock("@/lib/db", () => ({ async (strings: TemplateStringsArray, ...v: unknown[]) => { const sql = strings.join("?"); const amount = Number(v[0] ?? 0); - // Wave E — the operator-funded share moves in the same statement. + // The operator-funded share moves in the same statement. const operatorAmount = Number(v[1] ?? 0); if (sql.includes("total_tokens + ")) { ledgerTotal += amount; @@ -129,7 +129,7 @@ describe("runStatusCompletion — ledger accounting", () => { }); it("refuses a background generation at the job share while the chat still runs", async () => { - // Wave E — the automatic status/reference generators may spend at most + // The automatic status/reference generators may spend at most // half the operator ceiling, so the interactive chat still has a day left // when the jobs have had theirs. One token past the job share: ledgerTotal = 100_001; @@ -177,7 +177,7 @@ describe("runStatusCompletion — ledger accounting", () => { // "no provider configured" assessment, which a budget refusal is not. expect(runRawCompletionWithFallback).not.toHaveBeenCalled(); expect(result.kind).toBe("error"); - // v1.38.19 (Wave E, fix round 1) — the refusal has to SAY which ceiling it + // v1.38.19 — the refusal has to SAY which ceiling it // hit and whose. `{ totalAfter }` alone was the day's mixed total, which on // an operator refusal is not the counter that tripped; and the shared // `error` outcome cannot distinguish an exhausted background share from a diff --git a/src/lib/insights/briefing-provider.ts b/src/lib/insights/briefing-provider.ts index 16c2a2c42..1b8d15079 100644 --- a/src/lib/insights/briefing-provider.ts +++ b/src/lib/insights/briefing-provider.ts @@ -118,7 +118,7 @@ export async function runBriefingCompletion( args.maxTokens + Math.ceil((args.systemPrompt.length + args.userPrompt.length) / 4); - // v1.38.19 (Wave E) — a background surface: half the day's ceiling when the + // v1.38.19 — a background surface: half the day's ceiling when the // operator funds the chain. const jobCap = resolveDailyCapFor("job", args.chain); const reservation = await reserveBudget( diff --git a/src/lib/insights/status-provider.ts b/src/lib/insights/status-provider.ts index 6f63fba14..bb187ab5d 100644 --- a/src/lib/insights/status-provider.ts +++ b/src/lib/insights/status-provider.ts @@ -219,7 +219,7 @@ export async function runStatusCompletion( const dateKey = buildDateKey(); const estimatedTokens = maxTokens + Math.ceil((systemPrompt.length + userPrompt.length) / 4); - // v1.38.19 (Wave E) — a background surface: half the day's ceiling when the + // v1.38.19 — a background surface: half the day's ceiling when the // operator funds the chain. const jobCap = resolveDailyCapFor("job", chain); const reservation = await reserveBudget( @@ -236,7 +236,7 @@ export async function runStatusCompletion( // which callers cache as the settled "no provider configured" assessment. // The distinct annotation keeps the refusal observable even though the // result shape is shared. - // v1.38.19 (Wave E, fix round 1) — the shared `error` outcome cannot say + // v1.38.19 — the shared `error` outcome cannot say // WHY the generation stopped, so the annotation has to. `surface: "job"` // plus `cap` separates "this job used up the background share" from "the // whole day is spent", and `limit` says which counter tripped. The operator diff --git a/src/lib/jobs/__tests__/reaction-line-degradation.test.ts b/src/lib/jobs/__tests__/reaction-line-degradation.test.ts index 53b3d4ac6..ac9403751 100644 --- a/src/lib/jobs/__tests__/reaction-line-degradation.test.ts +++ b/src/lib/jobs/__tests__/reaction-line-degradation.test.ts @@ -199,7 +199,7 @@ beforeEach(async () => { isModuleEnabled.mockResolvedValue(true); chainRequiresServerManagedConsent.mockReturnValue(false); hasActiveConsentForSurface.mockResolvedValue(true); - // v1.38.19 (Wave E, fix round 1) — the claim reservation is no longer a + // v1.38.19 — the claim reservation is no longer a // second copy of the upsert; it calls the shared `reserveBudget` with the // transaction client. The spy therefore runs the REAL gate against the // mocked `$queryRaw`, so the rows a test hands back are the ledger the gate @@ -525,12 +525,12 @@ describe("reaction line — degradation", () => { }); it("a day the user's own plan paid for still admits the reaction", async () => { - // The 2026-09-11 shape, on the surface the wave forgot to convert: the - // operator's shared key answered 500 all morning and `codex` — his own - // ChatGPT plan — served everything, so the day's total is large and the - // operator's counter is empty. The reservation used to compare that total - // against the operator's ceiling and refuse, which is the exact defect the - // wave exists to remove, reproduced at HALF the old ceiling. + // The 2026-09-11 shape: the operator's shared key answered 500 all + // morning and `codex` — his own ChatGPT plan — served everything, so the + // day's total is large and the operator's counter is empty. The + // reservation used to compare that total against the operator's ceiling + // and refuse, locking the surface out over money the operator never spent + // — and at HALF the old ceiling, because this is a background surface. resolveProviderChain.mockResolvedValue([ { providerType: "openai", @@ -744,7 +744,7 @@ describe("reaction line — degradation", () => { }); /** - * v1.38.19 (Wave E, fix round 1) — a reservation is never stranded. + * v1.38.19 — a reservation is never stranded. * * Every path that crosses the provider boundary has to settle the ledger, and * settle it against the owner the reservation was actually booked to. Nothing diff --git a/src/lib/jobs/coach-nudge-ai.ts b/src/lib/jobs/coach-nudge-ai.ts index b991fb4ed..a2916d907 100644 --- a/src/lib/jobs/coach-nudge-ai.ts +++ b/src/lib/jobs/coach-nudge-ai.ts @@ -191,7 +191,7 @@ export const composeNudgeWithAI: ComposeNudgeWithAI = async (params) => { const budget = AI_BUDGETS.coachNudge; const maxTokens = budget.maxTokens ?? 160; - // v1.38.19 (Wave E) — a background surface: half the day's ceiling. + // v1.38.19 — a background surface: half the day's ceiling. const dailyCap = resolveDailyCapFor("job", chain); const dateKey = buildDateKey(); diff --git a/src/lib/jobs/data-arrival.ts b/src/lib/jobs/data-arrival.ts index 6c427ef33..1fa671331 100644 --- a/src/lib/jobs/data-arrival.ts +++ b/src/lib/jobs/data-arrival.ts @@ -62,7 +62,7 @@ interface LockedReaction { generationReservedTokens: number | null; generationBudgetDateKey: string | null; /** - * v1.38.19 (Wave E) — `"operator"` when the reservation below was booked to + * v1.38.19 — `"operator"` when the reservation below was booked to * the operator's counter, `"user"` otherwise, null on a row reserved before * the column existed. The refund has to reverse the SAME counters the * reservation moved, and there is no chain here to ask. @@ -125,7 +125,7 @@ async function claimReaction( current.generationBudgetDateKey != null && current.generationProviderInvokedAt == null ) { - // v1.38.19 (Wave E, fix round 1) — reverse BOTH counters, or neither. + // v1.38.19 — reverse BOTH counters, or neither. // // The reservation booked `operator_tokens` too whenever the chain's // primary was operator-funded. Refunding only the total left that share diff --git a/src/lib/jobs/document-content-index-backfill.ts b/src/lib/jobs/document-content-index-backfill.ts index d8e4ad718..53dec0f23 100644 --- a/src/lib/jobs/document-content-index-backfill.ts +++ b/src/lib/jobs/document-content-index-backfill.ts @@ -200,7 +200,7 @@ export async function runContentIndexBackfillForUser( // leaves every PDF out of "index all documents". const candidateMimes = [...IMAGE_MIMES, "application/pdf"]; - // v1.38.19 (Wave E) — a background surface: half the day's ceiling. + // v1.38.19 — a background surface: half the day's ceiling. const dailyCap = resolveDailyCapFor("job", [ { providerType: pick.entry.providerType }, ]); diff --git a/src/lib/jobs/document-summary.ts b/src/lib/jobs/document-summary.ts index 647115ee5..257586a01 100644 --- a/src/lib/jobs/document-summary.ts +++ b/src/lib/jobs/document-summary.ts @@ -191,7 +191,7 @@ export async function runDocumentSummaryJob( userId, AI_BUDGETS.documentSummary.maxTokens, dateKey, - // v1.38.19 (Wave E) — a background surface: half the day's ceiling. + // v1.38.19 — a background surface: half the day's ceiling. resolveDailyCapFor("job", [{ providerType: pick.entry.providerType }]), resolveCostOwner([{ providerType: pick.entry.providerType }]), "job", diff --git a/src/lib/jobs/reaction-line.ts b/src/lib/jobs/reaction-line.ts index 21dbb9bc4..35e994009 100644 --- a/src/lib/jobs/reaction-line.ts +++ b/src/lib/jobs/reaction-line.ts @@ -318,7 +318,7 @@ type ReactionReservation = { reserved: number; dateKey: string; /** - * v1.38.19 (Wave E) — the cost owner the reservation was booked to. It is + * v1.38.19 — the cost owner the reservation was booked to. It is * PERSISTED on the row (`generationCostOwner`), not recomputed: the chain is * re-resolved per run and the health ledger reorders it, so a resumed * reservation that asked the chain again could reverse a counter the @@ -363,15 +363,15 @@ async function reserveClaimBudget( }); if (stillOwned.count !== 1) throw new ReactionClaimLostError(); - // v1.38.19 (Wave E, fix round 1) — ONE reservation implementation. + // v1.38.19 — ONE reservation implementation. // // This surface used to carry its own copy of the upsert so the write could // ride the claim transaction. The copy drifted: it learned to WRITE // `operator_tokens` and kept gating on the day's mixed total, at the - // halved job ceiling — so on the operator's own 2026-09-11 row (1.2 M - // total, ~0 operator) the arrival reaction was refused all day, which is - // the exact defect this wave exists to remove, reproduced on the one - // surface the wave forgot to convert. `reserveBudget` takes the + // halved job ceiling — so on a row holding 1.2 M tokens of which almost + // none were the operator's (2026-09-11, the shared key answering 500 while + // the user's own plan served), the arrival reaction was refused all day + // over money the operator never spent. `reserveBudget` takes the // transaction client instead, so both writes still roll back together and // there is no second copy left to drift. const reservation = await reserveBudget( @@ -406,7 +406,7 @@ async function reserveClaimBudget( data: { generationReservedTokens: reserved, generationBudgetDateKey: dateKey, - // v1.38.19 (Wave E, fix round 1) — persist the cost owner with the + // v1.38.19 — persist the cost owner with the // amount. Two readers need it and neither can derive it: the resume // path below (the chain may have been reordered since) and the // supersede refund in `data-arrival.ts` (which has no chain at all). @@ -562,7 +562,7 @@ export async function runReactionLine( job.userId, revision, buildDateKey(), - // v1.38.19 (Wave E) — a background surface: half the day's ceiling. + // v1.38.19 — a background surface: half the day's ceiling. resolveDailyCapFor("job", chain), resolveCostOwner(chain), ); diff --git a/tests/integration/arrival-reaction-claim.test.ts b/tests/integration/arrival-reaction-claim.test.ts index 3a4284b0e..97398cb71 100644 --- a/tests/integration/arrival-reaction-claim.test.ts +++ b/tests/integration/arrival-reaction-claim.test.ts @@ -266,7 +266,7 @@ describe("ArrivalReaction — the day claim", () => { expect(usage).toMatchObject({ totalTokens: 280, messageCount: 1 }); }); - // v1.38.19 (Wave E, fix round 1) — the refund reverses BOTH counters. + // v1.38.19 — the refund reverses BOTH counters. // // The reservation booked `operator_tokens` whenever the chain's primary was // operator-funded. The supersede refund reversed only the total, and nothing diff --git a/tests/integration/coach-budget-owner.test.ts b/tests/integration/coach-budget-owner.test.ts index 08344af12..6e45304ea 100644 --- a/tests/integration/coach-budget-owner.test.ts +++ b/tests/integration/coach-budget-owner.test.ts @@ -1,5 +1,5 @@ /** - * v1.38.19 (Wave E) — the daily coach ledger attributes tokens to the COST + * v1.38.19 — the daily coach ledger attributes tokens to the COST * OWNER of the hop that actually served them. * * Production evidence (operator instance, 2026-09-11): the chat refused with @@ -219,12 +219,13 @@ describe("coach_usage.operator_tokens (real Postgres)", () => { expect(row?.operatorTokens).toBe(OPERATOR_COST_CAP); }); - // v1.38.19 (Wave E, fix round 1) — the incident, end to end. + // v1.38.19 — the incident, end to end. // // The 06:42Z row verbatim: 1.2 M tokens on the day, none of them the // operator's, on the chain `[admin-openai, codex, openai-compatible]`. The - // chat must open. Before the wave it was refused because the ledger had no - // owner dimension; this pins that the refusal cannot come back. + // chat must open. It was refused while the ledger had no owner dimension and + // counted every token against the operator's ceiling; this pins that the + // refusal cannot come back. it("does not refuse the chat for a day of background spend on the user's own plan", async () => { const { reserveBudget, resolveDailyCap, resolveCostOwner } = await import("@/lib/ai/coach/budget"); diff --git a/tests/integration/coach-budget-reservation.test.ts b/tests/integration/coach-budget-reservation.test.ts index 1f0bec052..2b4f28443 100644 --- a/tests/integration/coach-budget-reservation.test.ts +++ b/tests/integration/coach-budget-reservation.test.ts @@ -38,7 +38,7 @@ describe("reserveBudget (real Postgres)", () => { // Pre-spend 900 so a single 600-token reservation stays under the cap // but two concurrent ones cannot both be admitted. // - // v1.38.19 (Wave E) — the 900 sits on BOTH counters. The ledger grew a cost + // v1.38.19 — the 900 sits on BOTH counters. The ledger grew a cost // dimension: an operator-owned reservation is measured against // `operator_tokens`, so a fixture that pre-spends only the mixed total no // longer states its own premise ("900 already spent under this cap") and