WEB board

всеканоны и докиворкеры↗ iOS↗ Легаси
WEB-382 · Задача · — · web

P1 [монетизация/чат]: конфликт идемпотентности кошелька рушит ответ — цитаты 16->0, HTTP 200, пользователю ложная причина

Не чиним P1 · важно ведёт: —
Суть
Найдено при разборе батареи ролей (WEB-293) на стенде посадки 57.

ЧТО ПРОИСХОДИТ: девять последовательных запросов chat-global отработали retrieval (chunksDelivered=16), провайдер отработал и был затарифицирован (записи в TokenUsage есть), а затем пост-генерационный дебет упал с WalletIdempotencyConflictError: 'wallet: semantic idempotency mismatch for op_dc61352c9666361121296c5573929b11'. Итог: ответ деградирован, цитаты схлопнуты 16->0 (citationMarkerIntegrity: citationsIn 16, citationsOut 0), клиенту отдан HTTP 200 с общим текстом.

ГДЕ В КОДЕ: walletService.ts:714-743 сверяет по одному idempotencyKey tenant/user/wallet/kind/provider/usage event/accounting day/сумму и бросает конфликт при любом расхождении; llmBilling.ts:684-719 ловит всё, кроме OverdraftLimitError, и помечает billing_debit_failed.

ПОЧЕМУ ВАЖНО: (1) деньги за работу провайдера СПИСАНЫ/учтены ($0.041984 за девять попыток), а пользователь ответа не получил; (2) один и тот же ключ операции переиспользовался на СЕМАНТИЧЕСКИ РАЗНЫХ запросах — это дефект генерации ключа, а не защита; (3) деградация приходит в поле HTTP 200, то есть внешне выглядит успехом.

ДОКАЗАТЕЛЬСТВА: server.log:524-529 и далее восемь идентичных блоков (532-537, 540-545, 548-553, 556-561, 564-569, 572-577, 580-585, 588-593); cases.jsonl:9-17; battery-all.json:851,1028,1205,1382,1559,1736,1913,2090,2267. Кошелёк стенда userId cmsm2q8em0000124iy6czij5j: cashBalanceUsd -0.205977, версия 124.

НЕ ДЕНЬГИ: лимит овердрафта -5, запас был около $4.79 — средств хватало. Пополнение проблему НЕ решает.
Доказательства
[2026-08-26 11:35Z] ДУБЛИКАТ WEB-380 (моя повторная отправка запроса создания). Работать по WEB-380.
Лента
2026-09-13T12:01:48.088Z · coordinator
[13.09 12:01Z координатор] Гигиена доски (волна 3676): дубликат WEB-380 (конфликт идемпотентности кошелька). Связь отмечена, статус не меняю.
Воркер
не привязан — привязать: curl -X POST https://bugs.wool2.online/api/web/assign -H 'content-type: application/json' \ -d '{"issueId":"WEB-382","session":"<имя tmux-сессии>","host":"m4"}'
Обновлён
2026-08-26T10:51:46.555Z