WEB board

всеканоны и докиворкеры↗ iOS↗ Легаси
WEB-368 · Дефект · — · web

P1 [спенд-учёт]: TokenUsage row missing для judge-вызова → provider spend export FATAL

Закрыт P1 · важно ведёт: —
Суть
Финализатор батареи: ASSERT-FAIL TokenUsage row missing for req_036f4ad8... pass=claim_split_judge → per-provider cost.tsv rc=1 → FATAL. Т.е. существует платный вызов (claim_split_judge), который НЕ пишется в TokenUsage — тихая трата, класс правила 10. Найти путь claim_split_judge и обеспечить учёт КАЖДОГО платного вызова. Прогон: M4 /Volumes/M4Ext/ops/paidlegs/web293-full-20260825T151110Z (battery.log, battery-all.json, traces/). Контекст: WEB-293 корень (stale bundle) закрыт, скор 42/50.
Доказательства
📎 Трейл: тот же прогон, финализатор упал: ASSERT-FAIL TokenUsage row missing for req_036f4ad8... pass=claim_split_judge → cost.tsv rc=1 FATAL. Класс: тихая трата (правило 10) — платный вызов не в учёте. Найти путь claim_split_judge (grep src/lib/**).
[25.08 ~16:48Z] → in_progress: волна web368 на A1 (worktree wt-web368 от a453b16=посадка 55, codex luna xhigh, бриф /home/ubuntu/waves/WEB368-BRIEF.md → отчёт WEB368-REPORT.md, маркер WEB368_DONE). Проверки без платных вызовов (моки), полная батарея — отдельным прогоном на M4.
[25.08 ~17:45Z] → review: волна web368 сдала. Коммит 71f5907 (wt-web368 от a453b16). Корень: в shared shim ранний выход if(!armed && input.byok) return input.run() ДО учёта — люк мимо TokenUsage для legacy-BYOK путей; claim_split_judge = callLlmForExtraction в claimExtractor.ts. Фикс: contextTag=claim_split_judge через meterOpenAIClient→meterVendorCall→recordShimLedgerAndDebit→logTokenUsage; BYOK пишет receipt, chargeOwner=false (без дебита кошелька); unknown rate = NULL+pricing_status (не $0); сбой записи = маячок token_usage_write_failed, не throw. СВИП нашёл ещё 2 неучтённых пути: /api/transcribe (Whisper) и scanForConflicts — закрыты. Телефония намеренно не тронута (3 файла, изолированная зона). 13/13 mock-тестов (в т.ч. ровно одна TokenUsage-строка на claim_split), eslint 0. tsc: по новым строкам 0 диагностик, baseline-ошибки pre-existing. ВНИМАНИЕ ПРИЁМКЕ: затронут billing-контур — монетарно-смежное, проверять строго (accept36x кластер).
[25.08 ~17:55Z] ПРИЁМКА NOGO (accept36x) → in_progress. МОНЕТАРНЫЕ блокеры: B1 боевой BYOK-путь (raw key без credential/userId из claimExtractor) классифицируется UNKNOWN → в dark дебитует OWNER-кошелёк, в armed = DENY юзерского ключа; B2 сбой wallet-дебита рождает ВТОРУЮ cost-bearing TokenUsage-строку (diagnostic helper пишет в ту же таблицу с теми же cost/tokens) — завышение спенда; B3 contextTag=claim_split_judge жив только в no-reservation ветке — в reservation/armed пути terminal projector пишет event.method → тег мёртв ровно в боевом учётном пути. Ремонт: web368b.
[25.08 ~18:20Z] → review (3й заход): web368b, коммит 041d63e. Все 3 монетарных NOGO закрыты: B1 server-derived verified credential provenance из userId+ключа протянут в claim extraction/stance/rationale/Perplexity/OpenRouter BYOK → meterVendorCall (raw-shape больше НЕ дебитует owner в dark и не DENY в armed); B2 при сбое wallet-дебита вторая TokenUsage-строка ПОДАВЛЕНА (маячок+счётчик сохранены) — ровно одна cost-строка; B3 nullable contextTag на SpendReservation+TerminalSpendEvent, протянут через projection/repair, пишется+верифицируется на TokenUsage. МИГРАЦИЯ 20260825180000_web368b_context_tag_receipts — АДДИТИВНА (2 nullable колонки). Тесты: 5/5 accept-репро + 48/48 billing + 38/38 spendReservation, prisma validate ok, projection-hash ok. Re-accept строгий (монетарный).
[25.08 ~18:40Z] ✅ ПРИЁМКА GO (reaccept36x, sol, не автор): REACCEPT36X_368_GO. Каждый прошлый NOGO-факт закрыт с трассировкой до production route + независимый негатив (не только авторский тест). Готов к посадке 56.
[2026-08-25 20:55Z] 🔴 ПРОД-ИНЦИДЕНТ обратной совместимости: при деплое посадки 56 web368 пересчитал ИСТОРИЧЕСКОЕ terminal event (tse_2cddb9f0 от 20.08) новой projection-реализацией (hash 0d21a60c) → effect mismatch на token_usage → ПЕРЕОТКРЫЛ resolved corruption-строку sob_11c0e0b7 → paidReady=503 на ОБЕИХ версиях. accept36x/l56asm НЕ поймали (виден только на прод-исторических данных). Прод откатан на 55. Смена implementationHash НЕ должна ретроактивно переспроецировать старые события. Фикс web368d (luna): версионирование проекции — старое событие сверяется по своей записанной версии, новая реализация только к новым. web368 НЕ сажать до фикса.
[2026-08-25 21:22Z] web368d ГОТОВ (version-aware projection: событие сверяется по своей записанной версии, 0d21a60c в superseded-наборе, историческое resolved НЕ переоткрывает incident, same-version drift fail-closed; тесты 22/22+boundary rc=0; pinned 8b7336c8). Приёмка acc368d (luna) идёт: репро застрявшей прод-строки на копии данных → HEALS_ON_BOOT vs NEEDS_HEAL_STEP.
[2026-08-25 21:40Z] web368d ПРИНЯТ acc368d (независимо, приёмщик≠автор): GO. Version-aware projection корректна (22/22+23/23 негатив), boundary rc=0, acctfix-repair 30/30. HEALS_ON_BOOT=NO (застрявшая строка сама не закроется), но есть ШТАТНЫЙ heal (без SQL): scripts/acctfix-repair-incidents.ts --apply --only <event> (OPEN->REPAIRING->REPAIRED, сверка по исторической accepted-версии) затем /api/cron/spend-reconcile (REPAIRED->RESOLVED, reopenAccountingGate). Проверено на копии прод-данных: outbox resolved, backlog пуст. Восстановление платного = посадка 57 (сборка l57build от 8b4d4d8 запущена) + heal-step. web368d в review на re-accept? нет — GO, войдёт в посадку 57.
[2026-08-26 01:52Z] ✅ ПЛАТНОЕ ВОССТАНОВЛЕНО. healfix @9158e92 (app-level recheck, БЕЗ SQL по монетарным): dry-run на проде VERIFIED (эффекты доказаны под receipt identity 902b8748), --apply → FAILED_OPEN→APPLIED, incident OPEN→REPAIRING→REPAIRED, outbox ACCOUNTING_CORRUPTION→RESOLVED; затем /api/cron/spend-reconcile → gate reopened. ПРОД: paidReady=true, posture=enforce_ready, failures=[], health=200, event APPLIED, incident RESOLVED, открытых outbox 0, terminalMicros 17324 БЕЗ изменений (деньги не тронуты). Приёмка acchealfix: 7/7 денежных негативов корректно отклонены (расхождение 1 микро, смешанные identity, отсутствие wallet_debit, costMicros=NULL, чужая identity, гонка) + позитив; её NO-GO был по ошибочному критерию координатора (incident=RESOLVED vs штатная двухфазность REPAIRED→RESOLVED через reconcile). Туннели сняты, креды удалены.
Починено в
8b4d4d8
Воркер
не привязан — привязать: curl -X POST https://bugs.wool2.online/api/web/assign -H 'content-type: application/json' \ -d '{"issueId":"WEB-368","session":"<имя tmux-сессии>","host":"m4"}'
Обновлён
2026-08-26T01:50:44.529Z