Marc-André Bombeck
5f2a76ddbc
fix(coach): close the budget-gate TOCTOU and count burned tokens
...
The daily token cap was read before the provider call and recorded only
after a successful reply, so concurrent requests could all read
`spent < cap` and all hit the provider, and an empty/sentinel reply
recorded zero spend despite burning upstream tokens.
Reserve the budget atomically before the call: a single upsert-increment
returns the new total, so concurrent requests serialise on the row and
cannot both pass the cap; over-cap refunds the reservation and surfaces
`coach.budget.exceeded` as an SSE error frame. Reconcile the reservation
to the actual token count immediately after the provider returns —
before any empty/sentinel short-circuit — so burned tokens are always
recorded. An integration test proves two concurrent requests cannot both
exceed the cap and that an empty reply still records its cost.
2026-06-19 21:16:43 +02:00
..
2026-05-10 10:34:32 +02:00
2026-05-09 21:00:23 +02:00
2026-05-09 20:46:20 +02:00
2026-05-09 20:58:33 +02:00
2026-05-09 20:50:08 +02:00
2026-05-09 18:08:11 +02:00
2026-05-16 12:37:55 +02:00
2026-05-10 11:05:40 +02:00
2026-05-17 00:53:03 +02:00
2026-05-24 22:22:30 +02:00
2026-06-14 14:26:58 +02:00
2026-06-08 20:09:17 +02:00
2026-05-16 19:46:43 +02:00
2026-05-14 23:49:32 +02:00
2026-05-24 22:22:30 +02:00
2026-05-10 22:30:53 +02:00
2026-05-17 00:53:03 +02:00
2026-06-14 14:26:58 +02:00
2026-06-07 19:24:16 +02:00
2026-05-17 00:53:03 +02:00
2026-05-10 01:25:57 +02:00
2026-05-10 11:05:40 +02:00
2026-06-19 21:16:43 +02:00
2026-06-19 21:16:43 +02:00
2026-05-10 16:11:12 +02:00
2026-05-11 02:38:17 +02:00
2026-05-14 23:49:32 +02:00
2026-06-01 19:54:26 +02:00
2026-05-29 22:07:10 +02:00
2026-06-14 15:36:14 +02:00
2026-06-14 15:36:14 +02:00
2026-06-11 02:19:28 +02:00
2026-06-03 09:17:13 +02:00
2026-05-11 08:27:51 +02:00
2026-06-16 21:31:54 +02:00
2026-05-16 12:37:55 +02:00
2026-05-10 00:11:36 +02:00
2026-06-05 01:05:47 +02:00
2026-06-04 22:17:55 +02:00
2026-05-08 17:46:31 +02:00
2026-05-09 18:08:11 +02:00
2026-05-16 22:26:59 +02:00
2026-05-10 01:30:06 +02:00
2026-05-10 11:05:40 +02:00
2026-05-10 11:05:40 +02:00
2026-05-31 21:51:18 +02:00
2026-06-11 02:19:28 +02:00
2026-06-11 02:19:28 +02:00
2026-05-22 09:18:52 +02:00
2026-06-15 02:11:04 +02:00
2026-06-04 09:29:49 +02:00
2026-05-16 10:59:42 +02:00
2026-05-31 14:36:17 +02:00
2026-06-04 16:44:39 +02:00
2026-06-04 18:56:20 +02:00
2026-06-02 16:45:23 +02:00
2026-06-11 02:19:28 +02:00
2026-06-10 11:18:00 +02:00
2026-06-08 20:09:17 +02:00
2026-06-12 12:05:24 +02:00
2026-05-09 18:08:11 +02:00
2026-06-14 11:31:52 +02:00
2026-06-05 03:45:04 +02:00
2026-05-16 12:37:55 +02:00
2026-06-05 03:25:22 +02:00
2026-06-14 11:31:52 +02:00
2026-06-05 03:01:36 +02:00
2026-05-17 00:53:03 +02:00
2026-05-14 23:49:32 +02:00
2026-05-08 17:46:31 +02:00
2026-05-16 21:25:26 +02:00
2026-05-21 23:13:21 +02:00
2026-06-12 22:03:01 +02:00
2026-06-04 10:05:47 +02:00
2026-05-24 22:22:30 +02:00
2026-05-31 14:36:17 +02:00
2026-05-31 14:36:17 +02:00
2026-05-14 23:49:32 +02:00
2026-05-28 22:39:20 +02:00
2026-06-05 08:10:13 +02:00
2026-05-17 15:36:38 +02:00
2026-06-19 20:45:46 +02:00
2026-05-24 22:22:30 +02:00
2026-05-14 23:49:32 +02:00
2026-05-17 15:36:38 +02:00
2026-06-02 22:02:46 +02:00
2026-05-23 12:51:41 +02:00