WEB-674 · Дефект · billing · web
P1 [биллинг]: модели без строки в матрице цен молча тарифицируются по ставке чужого продукта (префиксный запасной путь)
Закрыт
P1 · важно
ведёт: —
Суть
## Находка волны 4307 (WEB-666), 19.09
Модель `gpt-4o-mini-transcribe` **отсутствует** в `MODEL_PRICING_MATRIX`. `resolveExactTokenPrice` — та самая функция, которой обязана пользоваться платная авторизация, — возвращает для неё `null`.
К платному пути модель всё равно допускается: `resolveModelPricing` сохраняет **legacy-префиксный запасной путь**, который молча наследует чатовую ставку `gpt-4o-mini` ($0.15 вход / $0.60 выход). Это ставка другого продукта, применённая к расшифровке речи.
### Почему это шире расшифровки
Префиксный запасной путь срабатывает для **любой** модели, чьё имя начинается с имени известной. То есть каждая новая модель семейства автоматически получает цену старшего брата — без строки в матрице и без чьего-либо решения. Мы не узнаем об этом, пока кто-нибудь не сверит деньги вручную.
### Дополнительно
`input_token_details` у этого пути делит вход на `audio_tokens` и `text_tokens` с **разными** ставками, а `TokenPrice` в продукте — одна входная ставка. Даже при наличии числа структура цены не позволяет оценить такой чек корректно.
Заверенный снимок `spend-estimator-registry-b2att-20260818.json` рядом с посекундной строкой называет `inputUsdPer1mTokens: 1.25`, но перевода токенов в секунды не даёт, и числа сами по себе не сходятся.
### Что нужно
1. Решить, должен ли префиксный запасной путь вообще существовать. Если да — он обязан быть явным и заметным (отказ или предупреждение), а не молчаливым.
2. Завести честные строки цен для моделей расшифровки.
3. Решить, как продукт хранит цену входа, разделённого на несколько видов токенов.
Доказательства: `4307TRANSCRIBE-evidence/` на Neo; ветка `refs/waves/4307/wave/4307-transcribe-actual` = `feff671f0` в зеркале A1.
Лента
2026-09-22T18:38:18.911Z · triage-neoРЕШЕНИЕ=todo
ОСНОВАНИЕ=Актуально: находка волны 4307 от 19.09.2026; в карточке нет приёмки/вердикта и production-посадки (только refs/waves/4307/... feff671f0); канон WEB-449 указывает l115o/68e25d8df (15.09.2026), legacy-префиксный расчёт не закрыт.
ЧТО НУЖНО=Ответственный не назначен; первый шаг — на текущей линии воспроизвести resolveExactTokenPrice/resolveModelPricing для gpt-4o-mini-transcribe и выбрать явный отказ или предупреждение, с доказательством.
triage-neo 4618
2026-09-23T06:44:44.447Z · coordinator[23.09 06:44Z координатор] ## 4667 (M1, Luna) — в работе: префиксный запасной путь цен → fail-closed на платном пути (`PRICING_PREFIX_FALLBACK=deny|warn`, умолчание deny, отказ `pricing_missing:<model>`), честные строки для gpt-4o-mini-transcribe/gpt-4o-transcribe/whisper-1 с раздельными audio/text ставками, старые строки без изменений; ветка `l115s-web674-pricing` от l115r.
2026-09-23T07:05:04.567Z · coordinator[23.09 07:05Z координатор] ## 4667 (M1, GO, 6/6) — принято: префиксный запасной путь цен теперь fail-closed на платном пути (`PRICING_PREFIX_FALLBACK=deny` по умолчанию → отказ `pricing_missing:<model>`; `warn` = прежнее поведение + предупреждение раз в минуту); точки использования: `src/lib/ai/pricing.ts:112-124,145-149`, `src/lib/billing/estimatorRegistry.ts:543-565`, `src/lib/billing/spendGuard.ts:779-784`. Добавлены 3 строки (gpt-4o-mini-transcribe, gpt-4o-transcribe, whisper-1) с раздельными audio/text входными ставками, старые строки без изменений; тесты 6/6 + 59/59, typecheck 0. Ветка A2 `l115s-web674-pricing` (2 коммита).
- ⚠️ ВЛАДЕЛЬЦУ ПРОВЕРИТЬ ЧИСЛА (взяты по памяти с карточки OpenAI без сети, `prices.md`): gpt-4o-mini-transcribe audio $3.00 / text $1.25 / out $5.00 за 1M; gpt-4o-transcribe $6.00 / $2.50 / $10.00; whisper-1 $0.006/мин. До подтверждения — в бой только режим `deny` (он безопасен независимо от чисел), строки сверить.
2026-09-23T07:53:47.150Z · coordinator[23.09 07:53Z координатор] ## 4670 (M1, независимая приёмка) — **4667 = NO-GO, CRITICAL F-4670-01**: при `ENABLE_SPEND_CAPS=1` и `PRICING_PREFIX_FALLBACK=warn` модель без точной строки, но с префиксом (`gpt-4o-mini-4670-prefix`) проходит платный путь как `paid approximate` — режим warn оказался лазейкой fail-open (`pricing.ts:113-124` → `estimatorRegistry.ts:544-565`, `spendGuard.ts:779-784`, metered/provider preflight). Остальное у 4667 зелёное (audio/text, паритет старых сумм). Репродуктор в `patches/0001`. Починка — 4672 (M1): платные резолверы exact-only при любом флаге, warn = предупреждение + отказ; статус ← in_progress.
2026-09-23T08:06:22.771Z · coordinator[23.09 08:06Z координатор] ## 4672 (M1, GO, 7/7) — принято: находка F-4670-01 закрыта — все 5 платных входов exact-only при любом значении `PRICING_PREFIX_FALLBACK` (warn = предупреждение + отказ), репродуктор 4670 красный до правки → зелёный после; тесты 4667 6/6, области `src/lib/ai`+`src/lib/billing` 88/88, typecheck 0. Ветка A2 `l115s-web674-pricing` = 4 коммита. Статус ← review. Открыто для владельца: сверить числа прайса транскрибации (`prices.md` 4667).
2026-09-23T08:29:25.774Z · coordinator[23.09 08:29Z координатор] ## 4673 (M1, независимая приёмка №2) — **4672 = NO-GO, CRITICAL F4673-01**: авторизация закрыта, но УЧЁТ ещё открыт — `estimateTokenUsageCost` зовёт display-резолвер и отдаёт префиксную сумму с ярлыком `pricingStatus: 'exact'`, её потребляет accounting/settlement. Входов в цену по коду 88 (display / paid-auth / accounting). Починка — 4675 (M1): учёт exact-only, `unknown_rate`, display не может попасть в учёт типом/guard. Статус остаётся in_progress.
2026-09-23T08:44:58.753Z · coordinator[23.09 08:44Z координатор] ## 4675 (M1, GO, 9/9) — принято: находка F4673-01 закрыта — платный учёт, settlement, резервирование, записи usage council/video и отчёт фактической стоимости перевода используют только точную матрицу; display-оценка не может попасть в учёт; репродуктор 4673 зелёный. Ветка A2 `l115s-web674-pricing` = 6 коммитов над l115r (4667 + 4672 + 4675). Статус ← review. Третья независимая приёмка денежного кода — в очереди (4681), до неё в кандидат l115s не включать.
2026-09-23T09:04:39.257Z · coordinator[23.09 09:04Z координатор] ## 4681 (M1, независимая приёмка №3) — **WEB-674 = NO-GO** (после 4675): (4, high) cron entity-resolution `src/lib/intelligence/entityResolution/llmAdjudication.ts:33,332` считает деньги через `calculateCost`, который в `pricing.ts` всё ещё берёт префикс; (5, medium) отказ `pricing_missing`/деградация объясняются пользователю как «достигнут лимит» (`spendGuard.ts:123-126`, `providers/base.ts:12-13`) — класс WEB-383; (6, medium) перевод: `unknown_rate` сопровождается числом `$0` (`translation/realCost.ts:60-79,149-151`). Починка — 4687 (M1). Статус ← in_progress.
2026-09-23T09:20:04.727Z · coordinator[23.09 09:20Z координатор] ## 4687 (M1, GO, 10/10) — принято: находки 4681 №4–6 закрыты — `calculateCost` теперь exact-only у обоих потребителей (entity-resolution cron включительно), формулировка отказа при `pricing_missing` не «лимит», перевод при `unknown_rate` даёт `null`, не `$0`; репродуктор 4681 зелёный; области 63/77 (14 skipped, 0 failed); typecheck 0. Ветка A2 `l115s-web674-pricing` = 7 коммитов над l115r. Статус ← review. Четвёртая независимая приёмка — вместе с пакетом сессии после 4688.
2026-09-23T09:50:26.313Z · coordinator[23.09 09:50Z координатор] ## 4689 (M1, ЧЕТВЁРТАЯ независимая приёмка, манифест 5/5 OK) — **WEB-674 = GO**: все 26 пунктов чек-листов 4670/4673/4681 повторены на финальном дереве (7 патчей) — PASS; 8 новых сквозных проверок PASS (модель без строки → отказ на seam spendGateCall до вызова вендора, без резерва/списания/usage; известные модели — суммы = base байт-в-байт; смешанный батч не портит остальные); находок 0. `READY_FOR_L115S=ДА`. Оговорка приёмки: без живого приложения/БД/сборки. Статус ← review (готов к кандидату l115s; включение — вместе со сводкой all4).
2026-09-25T08:42:18.288Z · coordinator[25.09 08:42Z координатор] # Ready board comment — WEB-674
2026-09-25 — POST-QA all9 `879094713e`: **CLOSE**.
Что сделано: paid authorization больше не наследует молча ставку модели-соседа по префиксу. `resolveExactTokenPrice` проверяет только явные matrix rows/aliases и возвращает null для неизвестной модели; transcribe audio/text input rates разделены. Файлы: `src/lib/billing/estimatorRegistry.ts:512-541`, `src/data/models.ts:1020,1059+`.
Коммиты all9: `da8586340`, `a7395c385`, `c3ae3adff`; regression `src/lib/billing/__tests__/web674PricingPrefixFallback.test.ts:32-168`. Команда и фактический RC=1/0 collected из-за include-конфига checkout записаны в `.../evidence/tests.md`; новый агент должен запускать тест с рабочим non-default Vitest profile и не менять дерево.
Траблшут: старый префикс (`gpt-4o-mini-transcribe` → chat `gpt-4o-mini`) скрывал чужую ставку; теперь unknown paid model видна и блокируется. Осталось: добавлять новые цены только явной строкой и проверять pricebook. Начало: WEB-449 → этот комментарий → exact resolver → test; бой/секреты не трогать.
Проверка координатора (09:5xZ, all9 879094713e, M1): перезапуск тестов 4982 правильным раннером (node --import tsx --test, Node 22): тесты тикета на all9 зелёные (4982 гонял vitest, который их не собирает). CLOSE подтверждён.
Воркер
не привязан — привязать:
curl -X POST https://bugs.wool2.online/api/web/assign -H 'content-type: application/json' \
-d '{"issueId":"WEB-674","session":"<имя tmux-сессии>","host":"m4"}'
Обновлён
2026-09-25T08:42:40.404Z