Remove the superseded budget read-then-write pair

enforceBudget and recordSpend have no callers left. Both defects they
carried recurred every time a surface copied them: a TOCTOU window
between the read and the bump, and a cap parameter defaulting to the
operator ceiling so an omitted argument rationed a self-hoster paying
for their own key. Deleting them means a future surface cannot reach
for the older, simpler-looking helper and reintroduce either.

getDailyTokenSpend goes with them — it existed to feed the read half.
The ledger-poisoning clamps recordSpend carried (NaN, negative,
fractional token counts) move to reserveBudget, where the property now
lives. Refresh the doc comments that still narrated the old flow.
This commit is contained in:
Marc-André Bombeck committed 2026-07-19 05:35:13 +02:00
1 parent de088febe6
commit b77a721474
5 files changed
+47 -132

No files matched your search

@@ -93,8 +93,12 @@ vi.mock("@/lib/ai/coach/coach-memory-shared", () => ({
vi.mock("@/lib/ai/coach/facts", () => ({ storeDeterministicFacts: vi.fn() }));
vi.mock("@/lib/ai/coach/budget", () => ({
buildDateKey: vi.fn(),
enforceBudget: vi.fn(),
recordSpend: vi.fn(),
reserveBudget: vi.fn(async () => ({
allowed: true,
reserved: 0,
totalAfter: 0,
})),
reconcileSpend: vi.fn(),
resolveDailyCap: vi.fn(() => 200_000),
}));
vi.mock("@/lib/ai/coach/refusal", () => ({ detectRefusal: vi.fn() }));
+3 -2
View File
@@ -9,8 +9,9 @@
* Behaviour:
* 1. requireAuth() — cookie session OR bearer token (iOS app).
* 2. Validate body with `coachChatRequestSchema`.
* 3. enforceBudget() — 429 with `coach.budget.exceeded` when the
* user has already burned the day's token cap.
* 3. reserveBudget(resolveDailyCap(chain)) — 429 with
* `coach.budget.exceeded` when the day's token cap for the
* credential that would pay for the call is already burned.
* 4. detectRefusal() — pattern-based prompt-injection +
* off-topic guard. Refusal emits a single `token` frame with
* the localised refusal copy and a `done` frame; never hits a