WEB board

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

P1 [биллинг]: транзиентный таймаут транзакции проекции (5 с) под нагрузкой создаёт ACCOUNTING_CORRUPTION и закрывает платный гейт всем

Закрыт P1 · важно ведёт: —
Суть
## Суть одной фразой
Транзиентный таймаут проекции под нагрузкой закрывал платный гейт всем (paid 503).

## Где мы сейчас (13.09.2026)
Фикс посажен и пережил три рецидива (02.09/03.09/05.09), после 05.09 рецидивов нет: settlement tx 30 000/15 000 мс (SETTLEMENT_TRANSACTION_TIMEOUT_MS) + retry на P2028 ДО записи в outbox + auto-heal (settlementAutoHeal, resolvedBy=spend-reconcile:auto-heal). Осталась финальная ЖИВАЯ проверка settlement на l115c (оба бэкенда) с runtime-доказательством. Source-фикс подтверждён независимо 12.09 (волна 3587, node:test на l115g), но живая проверка была вне скоупа той волны.

## Хронология
1133/1142/1144 (проекция) → 1365/1414 (blast-radius, wallet race) → l95 рецидив → 1579/e4d8fdbe (settlement 30с+retry, приёмка 1606 GO) → 05.09 рецидив → 1933 c4c3f811 (15с/auto-heal, приёмка GO) → l109 (135103a33) 12:31Z → большой PDF 2173/2173b (l113d) → мойки 12.09 (3567), 13.09 (3634).

## Карта документов и кода
src/lib/billing/settlementTransaction.ts, projectionErrorClassification.ts, settlementAutoHeal.ts; тесты web467SettlementTxRetry/AutoHeal; отчёты ACCSETTLEMENTAUTOHEAL.

## Остаток
- Живая проверка settlement на l115c (оба бэкенда) с runtime-доказательством: 0 OPEN SETTLEMENT_FAILED с транзиентным errorCode старше 15 мин, resolvedBy=spend-reconcile:auto-heal, paid ready непрерывно, SETTLEMENT_TRANSACTION_TIMEOUT_MS задан в env прода — координатор (⛔ прод read-only).

## Критерий закрытия
Живая проверка на l115c подтверждает самовосстановление + paid ready непрерывно в окне с реальными расчётами; SETTLEMENT_TRANSACTION_TIMEOUT_MS задан в env прода.
---
_история_ — прежний текст тела (до 13.09.2026) координатор сохраняет ниже этой строки без изменений (append-only по WEB-449).

---

## история (тело до 13.09.2026)

02.09 04:40Z (l87): SERVICE-событие воркера индексации tse_740a8a1e (13 micro-USD) упало на проекции: PrismaClientKnownRequestError «Transaction already closed: timeout 5000 ms, 56882 ms passed» (ledgerEntry.findUnique внутри interactive-транзакции проекции) — бокс был под нагрузкой (батарея + 4 волны). Итог: TerminalProjectionIncident OPEN + SpendSettlementOutbox ACCOUNTING_CORRUPTION → paidReady 503 для всех; реконсилер перепроецировал событие с первой попытки (APPLIED), инцидент закрыт acctfix-repair вручную. Класс: ТРАНЗИЕНТНАЯ ошибка БД трактуется как порча учёта (durable-unhealthy). Нужно: (а) transient-ошибки (transaction expired/closed, timeout, connection) → retry/pending, а не incident; (б) interactive transaction timeout для проекции > 5 с (или проекция не в горячем пути settle, только реконсилером); (в) авто-repair инцидента, если реконсилер затем спроецировал событие чисто (сейчас требует ручного acctfix-repair). Связано: WEB-461 (SERVICE-плательщик), память service-payer-projection-gap-paid-503.

## ДЛЯ НУЛЕВОГО АГЕНТА (обновлено 2026-09-05 UTC)
- **Суть одной строкой:** P1 [биллинг]: транзиентный таймаут транзакции проекции (5 с) под нагрузкой создаёт ACCOUNTINGCORRUPTION и закрывает платный гейт всем
- **Текущее состояние:** статус: in_progress; 02.09 04:40Z (l87): SERVICE-событие воркера индексации tse740a8a1e (13 micro-USD) упало на проекции: PrismaClientKnownRequestError «Transaction already closed: timeout 5000 ms, 56882 ms passed» (ledgerEntry.findUnique внутри interactive-транзакции проекции) — бокс был под нагрузкой (батарея + 4 в… | [02.09 07:50Z Фабл] Второе срабатывание за час (04:44Z A1): воркер (oneshot --worker, запущен таймером до его остановки) индексировал «Войну и мир» QA-учётки — 248 SERVICE-событий rag.embeddings за час, 247 APPLIED, одно упало на $queryRaw в expired-транзакции (5 с) → tpi8b95fe56 + sobf8b9d6ba → … | [02.09 09:15Z Фабл] 1133-projtimeout сдан (16e326884b): транзиентные ошибки проекции (P2028, timeout, reset, deadlock, serialization) → retry ×5, interactive tx timeout 30s/maxWait 10s; семантические ошибки по-прежнему fail-closed (incident + ACCOUNTINGCORRUPTION); реконсилер после чистого APPLIE… | [02.09 06:15Z Фабл] Приёмка 1138-accprojtimeout (Luna, реальный PG, одноразовая БД): REJECT. PASS: реальный P2028 → retry → APPLIED без инцидента; 5 transient подряд → PENDINGACTIVE retry-later (без тихого успеха/двойного дебета); effect/hash/counter mismatch → FAILEDOPEN + ACCOUNTINGCORRUPTION; … | [02.09 06:45Z Фабл] 1142-projtimeout2 сдан (6053578047): общий isRetryableProjectionErrorCode (projectionErrorClassification.ts); autoRepairTerminalProjectionIncident читает сохранённый errorCode из PG и чинит автоматически только transient-класс; семантические/неизвестные коды остаются OPEN с ло… | [02.09 06:50Z Фабл] Ре-приёмка 1144-accprojtimeout2: PASS (6053578047; real PG: transient → retry → APPLIED; transient incident → авто-repair; неизвестный errorCode → OPEN; 5 transient → PENDINGACTIVE без дебета; semantic effectmismatch → OPEN + ACCOUNTINGCORRUPTION + paidHealth.ready=false + лог… | [A1 21:20 Фабл] Волна 1365-web467projtimeout поставлена (A1, Sol medium): транзиентные ошибки проекции (таймаут транзакции) → retry, не ACCOUNTINGCORRUPTION; короткая транзакция; blast-radius per-payer вместо 503 всем. | [A1 23:55 Фабл] Волна 1365-web467projtimeout сдана: коммит 16ddd1b303 (транзиент → retry, короткая транзакция проекции, blast-radius per-payer). Отчёт: A1 /home/ubuntu/waves/WEB467PROJTIMEOUT-REPORT.md. Приёмка 1395 (A1, Sol medium, в очереди) — деньги: сомнение = NO-GO.
- **Кто работал:**
- 2026-09-02T04:46:34.609Z — Fable — [02.09 07:50Z Фабл] Второе срабатывание за час (04:44Z A1): воркер (oneshot --worker, запущен таймером до его остановки) индексировал «Войну и мир» QA-учётки — 248 SERVICE-событий rag.embeddings за час, 247 APPLIED, одно
- 2026-09-02T05:07:15.908Z — Fable — [02.09 09:15Z Фабл] 1133-projtimeout сдан (16e326884b): транзиентные ошибки проекции (P2028, timeout, reset, deadlock, serialization) → retry ×5, interactive tx timeout 30s/maxWait 10s; семантические ошибки по-прежнему f
- 2026-09-02T05:43:56.218Z — Fable — [02.09 06:15Z Фабл] Приёмка 1138-accprojtimeout (Luna, реальный PG, одноразовая БД): REJECT. PASS: реальный P2028 → retry → APPLIED без инцидента; 5 transient подряд → PENDINGACTIVE retry-later (без тихого успеха/двойног
- 2026-09-02T06:02:29.677Z — Fable — [02.09 06:45Z Фабл] 1142-projtimeout2 сдан (6053578047): общий isRetryableProjectionErrorCode (projectionErrorClassification.ts); autoRepairTerminalProjectionIncident читает сохранённый errorCode из PG и чинит автоматиче
- 2026-09-02T06:33:29.926Z — Fable — [02.09 06:50Z Фабл] Ре-приёмка 1144-accprojtimeout2: PASS (6053578047; real PG: transient → retry → APPLIED; transient incident → авто-repair; неизвестный errorCode → OPEN; 5 transient → PENDINGACTIVE без дебета; semanti
- 2026-09-02T21:15:36.928Z — Fable — [A1 21:20 Фабл] Волна 1365-web467projtimeout поставлена (A1, Sol medium): транзиентные ошибки проекции (таймаут транзакции) → retry, не ACCOUNTINGCORRUPTION; короткая транзакция; blast-radius per-payer вместо 503 всем.
- 2026-09-02T22:23:50.563Z — Fable — [A1 23:55 Фабл] Волна 1365-web467projtimeout сдана: коммит 16ddd1b303 (транзиент → retry, короткая транзакция проекции, blast-radius per-payer). Отчёт: A1 /home/ubuntu/waves/WEB467PROJTIMEOUT-REPORT.md. Приёмка 1395 (A1,
- 2026-09-02T23:33:56.041Z — Fable — [A1 04:05 Фабл] Приёмка 1395-accweb467projtimeout = NO-GO (отчёт A1 /home/ubuntu/waves/ACCWEB467PROJTIMEOUT-REPORT.md). PASS: транзиент → retry/идемпотентность, реальная коррупция по-прежнему открывает ACCOUNTINGCORRUPTI
- 2026-09-03T00:40:20.948Z — Fable — [A1 09:30 Фабл] Волна 1414-web467projtimeout2 сдана: коммит 85cfc67044 (инвалидация кэша gate при открытии инцидента; get-or-create wallet под уникальным ограничением/advisory lock; нагрузочный тест на PG16). Отчёт: A1 /
- 2026-09-03T02:52:03.351Z — Fable — [A1 Фабл] Приёмка 1437-accweb467projtimeout2 = GO (независимая, Sol). Линия projtimeout2 (волна 1414) идёт в l95-candidate поверх базы l95 (после SIP-слияния 1448).
- 2026-09-03T03:20:26.021Z — Fable — [Фабл] ОПАСНОСТЬ ПОСАДКИ: projtimeout2 (85cfc670) регенерировал projectionImplementationHash.generated.ts от базы l93 и ВЫБРОСИЛ superseded-хэши e4b2e336, 8d41ed45, 250c99ec, f0df5906 — три из них есть в живом списке про
- 2026-09-03T03:20:47.931Z — Fable — [A1 Фабл] Факты прода (read-only): TerminalSpendProjectionReceipt по хэшам — 8d41ed45 ×3282, e4b2e336 ×7, 250c99ec ×1 — все три выброшены из superseded в projtimeout2; при этом текущий l94 не знает 8b7336c8 (×926), fb0ce
- 2026-09-03T03:48:56.671Z — Fable — [A2 Фабл] Сборка l95 встала на гейте P19 (WEB-093): check-migration-compatibility → «baseline is stale» — миграция web467pooledwalletsingleton добавлена без обновления prisma/migration-compatibility-manifest.json; приёмк
- 2026-09-03T05:15:09.291Z — Fable — [A1 Фабл] ПОСАЖЕНО в l95: миграция web467pooledwalletsingleton применена 05:08Z (индекс UserBalancepooledtenantkey), paid enforceready, projection-хэши сохранены (1438d).
- 2026-09-03T07:17:22.015Z — coordinator — WEB-467 РЕЦИДИВ 03.09 07:07Z на l95 (f7b9a4fd): под нагрузкой (hetzbk-base 07:00 + 3 волны + батарея ролей → load 11 на 4 vCPU) settlement-транзакция (resv4ab967b1…, openai gpt-5.4-mini, 28923 µUSD) вылетела по interacti
- 2026-09-03T07:49:02.406Z — coordinator — ВОССТАНОВЛЕНО 03.09 07:53Z. Полный рецепт (l95, paid 503 07:08→07:53Z): (1) снять нагрузку (renice/ionice волн и hetzbk; drop-ins Nice/CPUWeight); (2) дождаться expiry резервации → sweep реконсилера (settle-at-max) — act
- 2026-09-03T16:16:10.033Z — coordinator — 03.09 21:45 IST. Круг 2 роздан: волна 1579-txtimeout2 идёт на A1 (старт 16:14:44Z, модель Luna xhigh). Основание — сегодняшний рецидив 07:08–07:53Z: расчёт упал с «Transaction API error: Transaction already closed … time
- 2026-09-03T17:53:18.717Z — coordinator — Круг 2 сдан: коммит b26c9684 «fix(billing): recover transient settlement timeouts». Приёмка = волна 1584-accpaidgate на A1: потолок интерактивной транзакции, ретрай на P2028 ДО записи в outbox, автозакрытие причин catego
- 2026-09-03T19:23:21.155Z — coordinator — Приёмка 1584 = NO-GO. Требуется узко доказуемая миграционно-реконсиляционная обработка старых manual causes от P2028 при пустом бэклоге: сегодня из-за их отсутствия платный контур стоял 45 минут и открывался вручную чере
- 2026-09-03T22:36:19.679Z — coordinator — Круг 2 сдан в составе совместного коммита e4d8fdbe. Приёмка = волна 1606-accpaidgate3 на A1. Проверяется: потолок interactive-транзакции поднят или транзакция разбита, ретрай на P2028 ДО записи в outbox, транзиент восста
- 2026-09-03T22:59:33.105Z — coordinator — Приёмка 1606 = GO для e4d8fdbe. Потолок settlement-транзакции поднят с 5 с (Prisma default) до SETTLEMENTTRANSACTIONTIMEOUTMS=30000; ретрай на три попытки стоит вокруг runSettlementTransaction ДО внешнего catch, одиночны
- 2026-09-05T04:04:32.698Z — coordinator — [05.09 04:04Z координатор] ИНЦИДЕНТ 05.09 03:38–04:13Z (третий случай класса): весь платный AI на проде отказывал «AI spending accounting is being repaired». /api/ready/paid = unready/durable-unhealthy, failures accounti
- 2026-09-05T04:44:05.556Z — coordinator — [05.09 04:44Z координатор] Самолечение (волна 1933 SETTLEMENTAUTOHEAL, A2, opus) = GO по автору, коммит c4c3f811 поверх 6930c683: settlement tx 15 000 мс/maxWait 5 000 (худший случай был 9 944 мс); один ретрай при «Trans
- 2026-09-05T08:46:05.995Z — coordinator — [05.09 08:46Z координатор] ПРИЁМКА (ACCSETTLEMENTAUTOHEAL, A2, opus, настоящий PostgreSQL) = GO, коммит c4c3f811 (на l107 cherry-pick чисто, побайтно те же файлы): tx 15 000/5 000 мс; ровно один повтор ДО записи SETTLEME
- 2026-09-05T12:40:28.157Z — coordinator — [05.09 12:40Z координатор] l109 (135103a33) СЕЛ на прод 12:31Z: оба бэкенда, edge ×8, polling ok, paid ok, воркеры на l109; hetzbk-артефакт опубликован, env-line l109, Pi переключается. Самолечение учёта (78a721b5) в жив
- **Ветки/бандлы/отчёты:** /home/ubuntu/waves/PROJTIMEOUT-REPORT.md;, /home/ubuntu/waves/ACCPROJTIMEOUT-REPORT.md., /home/ubuntu/waves/WEB467PROJTIMEOUT-REPORT.md., /home/ubuntu/waves/ACCWEB467PROJTIMEOUT-REPORT.md, /home/ubuntu/waves/WEB467PROJTIMEOUT2-REPORT.md.; sha: 16e326884b, fd1057d4, 6053578047, fdfcf388, 16ddd1b303, 16ddd1b3, 85cfc67044, 85cfc670
- **KNOWN ISSUES / ТРАБЛШУТИНГ:**
- 02.09 04:40Z (l87): SERVICE-событие воркера индексации tse740a8a1e (13 micro-USD) упало на проекции: PrismaClientKnownRequestError «Transaction already closed: timeout 5000 ms, 56882 ms passed» (ledgerEntry.findUnique внутри interactive-транзакции проекции) — бокс был под нагрузкой (батарея + 4 в…
- [02.09 07:50Z Фабл] Второе срабатывание за час (04:44Z A1): воркер (oneshot --worker, запущен таймером до его остановки) индексировал «Войну и мир» QA-учётки — 248 SERVICE-событий rag.embeddings за час, 247 APPLIED, одно упало на $queryRaw в expired-транзакции (5 с) → tpi8b95fe56 + sobf8b9d6ba → …
- [02.09 09:15Z Фабл] 1133-projtimeout сдан (16e326884b): транзиентные ошибки проекции (P2028, timeout, reset, deadlock, serialization) → retry ×5, interactive tx timeout 30s/maxWait 10s; семантические ошибки по-прежнему fail-closed (incident + ACCOUNTINGCORRUPTION); реконсилер после чистого APPLIE…
- [02.09 06:15Z Фабл] Приёмка 1138-accprojtimeout (Luna, реальный PG, одноразовая БД): REJECT. PASS: реальный P2028 → retry → APPLIED без инцидента; 5 transient подряд → PENDINGACTIVE retry-later (без тихого успеха/двойного дебета); effect/hash/counter mismatch → FAILEDOPEN + ACCOUNTINGCORRUPTION; …
- [02.09 06:45Z Фабл] 1142-projtimeout2 сдан (6053578047): общий isRetryableProjectionErrorCode (projectionErrorClassification.ts); autoRepairTerminalProjectionIncident читает сохранённый errorCode из PG и чинит автоматически только transient-класс; семантические/неизвестные коды остаются OPEN с ло…
- [02.09 06:50Z Фабл] Ре-приёмка 1144-accprojtimeout2: PASS (6053578047; real PG: transient → retry → APPLIED; transient incident → авто-repair; неизвестный errorCode → OPEN; 5 transient → PENDINGACTIVE без дебета; semantic effectmismatch → OPEN + ACCOUNTINGCORRUPTION + paidHealth.ready=false + лог…
- [A1 21:20 Фабл] Волна 1365-web467projtimeout поставлена (A1, Sol medium): транзиентные ошибки проекции (таймаут транзакции) → retry, не ACCOUNTINGCORRUPTION; короткая транзакция; blast-radius per-payer вместо 503 всем.
- [A1 23:55 Фабл] Волна 1365-web467projtimeout сдана: коммит 16ddd1b303 (транзиент → retry, короткая транзакция проекции, blast-radius per-payer). Отчёт: A1 /home/ubuntu/waves/WEB467PROJTIMEOUT-REPORT.md. Приёмка 1395 (A1, Sol medium, в очереди) — деньги: сомнение = NO-GO.
- **Эволюция:**
- 2026-09-02 → [02.09 07:50Z Фабл] Второе срабатывание за час (04:44Z A1): воркер (oneshot --worker, запущен таймером до его остановки) индексировал «Войну и мир» QA-учётки — 248 SERVICE-событий rag.embeddings за час, 247 APPLIED, одно упало на $queryRaw в expired-транзакции
- 2026-09-02 → [02.09 09:15Z Фабл] 1133-projtimeout сдан (16e326884b): транзиентные ошибки проекции (P2028, timeout, reset, deadlock, serialization) → retry ×5, interactive tx timeout 30s/maxWait 10s; семантические ошибки по-прежнему fail-closed (incident + ACCOUNTINGCORRUPT
- 2026-09-02 → [02.09 06:15Z Фабл] Приёмка 1138-accprojtimeout (Luna, реальный PG, одноразовая БД): REJECT. PASS: реальный P2028 → retry → APPLIED без инцидента; 5 transient подряд → PENDINGACTIVE retry-later (без тихого успеха/двойного дебета); effect/hash/counter mismatch
- 2026-09-02 → [02.09 06:45Z Фабл] 1142-projtimeout2 сдан (6053578047): общий isRetryableProjectionErrorCode (projectionErrorClassification.ts); autoRepairTerminalProjectionIncident читает сохранённый errorCode из PG и чинит автоматически только transient-класс; семантически
- 2026-09-02 → [02.09 06:50Z Фабл] Ре-приёмка 1144-accprojtimeout2: PASS (6053578047; real PG: transient → retry → APPLIED; transient incident → авто-repair; неизвестный errorCode → OPEN; 5 transient → PENDINGACTIVE без дебета; semantic effectmismatch → OPEN + ACCOUNTINGCORR
- 2026-09-02 → [A1 21:20 Фабл] Волна 1365-web467projtimeout поставлена (A1, Sol medium): транзиентные ошибки проекции (таймаут транзакции) → retry, не ACCOUNTINGCORRUPTION; короткая транзакция; blast-radius per-payer вместо 503 всем.
- 2026-09-02 → [A1 23:55 Фабл] Волна 1365-web467projtimeout сдана: коммит 16ddd1b303 (транзиент → retry, короткая транзакция проекции, blast-radius per-payer). Отчёт: A1 /home/ubuntu/waves/WEB467PROJTIMEOUT-REPORT.md. Приёмка 1395 (A1, Sol medium, в очереди) — деньги: сомнен
- 2026-09-02 → [A1 04:05 Фабл] Приёмка 1395-accweb467projtimeout = NO-GO (отчёт A1 /home/ubuntu/waves/ACCWEB467PROJTIMEOUT-REPORT.md). PASS: транзиент → retry/идемпотентность, реальная коррупция по-прежнему открывает ACCOUNTINGCORRUPTION, semantic isolation. BLOCKER 1: payer
- 2026-09-03 → [A1 09:30 Фабл] Волна 1414-web467projtimeout2 сдана: коммит 85cfc67044 (инвалидация кэша gate при открытии инцидента; get-or-create wallet под уникальным ограничением/advisory lock; нагрузочный тест на PG16). Отчёт: A1 /home/ubuntu/waves/WEB467PROJTIMEOUT2-REP
- 2026-09-03 → [A1 Фабл] Приёмка 1437-accweb467projtimeout2 = GO (независимая, Sol). Линия projtimeout2 (волна 1414) идёт в l95-candidate поверх базы l95 (после SIP-слияния 1448).
- 02.09 04:40Z (l87): SERVICE-событие воркера индексации tse740a8a1e (13 micro-USD) упало на проекции: PrismaClientKnownRequestError «Transaction already closed: timeout 5000 ms, 56882 ms passed» (ledgerEntry.findUnique внутри interactive-транзакции проекции) — бокс был под нагрузкой (батарея + 4 в…
- [02.09 07:50Z Фабл] Второе срабатывание за час (04:44Z A1): воркер (oneshot --worker, запущен таймером до его остановки) индексировал «Войну и мир» QA-учётки — 248 SERVICE-событий rag.embeddings за час, 247 APPLIED, одно упало на $queryRaw в expired-транзакции (5 с) → tpi8b95fe56 + sobf8b9d6ba → …
- [02.09 09:15Z Фабл] 1133-projtimeout сдан (16e326884b): транзиентные ошибки проекции (P2028, timeout, reset, deadlock, serialization) → retry ×5, interactive tx timeout 30s/maxWait 10s; семантические ошибки по-прежнему fail-closed (incident + ACCOUNTINGCORRUPTION); реконсилер после чистого APPLIE…
- [02.09 06:15Z Фабл] Приёмка 1138-accprojtimeout (Luna, реальный PG, одноразовая БД): REJECT. PASS: реальный P2028 → retry → APPLIED без инцидента; 5 transient подряд → PENDINGACTIVE retry-later (без тихого успеха/двойного дебета); effect/hash/counter mismatch → FAILEDOPEN + ACCOUNTINGCORRUPTION; …
- [02.09 06:45Z Фабл] 1142-projtimeout2 сдан (6053578047): общий isRetryableProjectionErrorCode (projectionErrorClassification.ts); autoRepairTerminalProjectionIncident читает сохранённый errorCode из PG и чинит автоматически только transient-класс; семантические/неизвестные коды остаются OPEN с ло…
- [02.09 06:50Z Фабл] Ре-приёмка 1144-accprojtimeout2: PASS (6053578047; real PG: transient → retry → APPLIED; transient incident → авто-repair; неизвестный errorCode → OPEN; 5 transient → PENDINGACTIVE без дебета; semantic effectmismatch → OPEN + ACCOUNTINGCORRUPTION + paidHealth.ready=false + лог…
- **Следующий шаг:** 02.09 04:40Z (l87): SERVICE-событие воркера индексации tse740a8a1e (13 micro-USD) упало на проекции: PrismaClientKnownRequestError «Transaction already closed: timeout 5000 ms, 56882 ms passed» (ledgerEntry.findUnique внутри interactive-транзакции проекции) — бокс был под нагрузкой (батарея + 4 в… | Приёмка 1606 = GO для e4d8fdbe. Потолок settlement-транзакции поднят с 5 с (Prisma default) до SETTLEMENTTRANSACTIONTIMEOUTMS=30000; ретрай на три попытки стоит вокруг runSettlementTransaction ДО внешнего catch, одиночный P2028 больше не пишет outbox; исчерпанный P2028 пишется как SETTLEMENTFAILE…
Лента
2026-09-02T04:46:34.609Z · Fable
[02.09 07:50Z Фабл] Второе срабатывание за час (04:44Z A1): воркер (oneshot --worker, запущен таймером до его остановки) индексировал «Войну и мир» QA-учётки — 248 SERVICE-событий rag.embeddings за час, 247 APPLIED, одно упало на $queryRaw в expired-транзакции (5 с) → tpi_8b95fe56 + sob_f8b9d6ba → paid 503 ~10 мин. Реконсилер: событие уже APPLIED; acctfix-repair --apply закрыл; paid 200. Служба воркера остановлена (systemctl stop), таймер выключен. Вывод для фикса: под нагрузкой проекция в горячем пути регулярно не укладывается в 5 с — нужен (а) classify transient → retry без incident, (б) timeout ≥30 с / облегчить транзакцию, (в) авто-repair после чистой перепроекции.
2026-09-02T05:07:15.908Z · Fable
[02.09 09:15Z Фабл] 1133-projtimeout сдан (16e326884b): транзиентные ошибки проекции (P2028, timeout, reset, deadlock, serialization) → retry ×5, interactive tx timeout 30s/maxWait 10s; семантические ошибки по-прежнему fail-closed (incident + ACCOUNTING_CORRUPTION); реконсилер после чистого APPLIED сам вызывает begin/completeTerminalProjectionRepair. Суммы/cap не тронуты. projectionImplementationHash изменился → при сборке l88 нужен новый live hash в PROJECTION_LIVE_IMPLEMENTATION_HASHES (память monetary-projection-versioning-hazard). Автор гонял на реальном PG (одноразовая БД). Приёмка 1138 (Luna xhigh, негативы: 5 отказов подряд, семантика, двойной дебет, триггер). Отчёт A1 /home/ubuntu/waves/PROJTIMEOUT-REPORT.md; бандл mirrors/lines/projtimeout-1133-16e326884b.bundle. Воркер индексации остаётся выключен до посадки l88.
2026-09-02T05:43:56.218Z · Fable
[02.09 06:15Z Фабл] Приёмка 1138-accprojtimeout (Luna, реальный PG, одноразовая БД): REJECT. PASS: реальный P2028 → retry → APPLIED без инцидента; 5 transient подряд → PENDING_ACTIVE retry-later (без тихого успеха/двойного дебета); effect/hash/counter mismatch → FAILED_OPEN + ACCOUNTING_CORRUPTION; идемпотентность SERVICE userId NULL; ручной acctfix-repair. BLOCKER: авто-repair реконсилера (settlementLatch.ts:851) закрывает и семантический terminal_projection_effect_mismatch (incident REPAIRED, outbox RESOLVED) — авто-repair не ограничен transient-классом. Новый hash проекции fd1057d4…42c6. Доработка 1142-projtimeout2 (гейт по errorCode инцидента, тест на реальном PG). Отчёт A1 /home/ubuntu/waves/ACCPROJTIMEOUT-REPORT.md.
2026-09-02T06:02:29.677Z · Fable
[02.09 06:45Z Фабл] 1142-projtimeout2 сдан (6053578047): общий isRetryableProjectionErrorCode (projectionErrorClassification.ts); autoRepairTerminalProjectionIncident читает сохранённый errorCode из PG и чинит автоматически только transient-класс; семантические/неизвестные коды остаются OPEN с логом; real-PG тест: transient auto-repair, semantic OPEN + ACCOUNTING_CORRUPTION OPEN + paidHealth.ready=false, ручной acctfix. Новый hash проекции fdfcf388…b581; hash-chain verifier PASS. Ре-приёмка 1144 (codex, реальный PG, + негатив «неизвестный errorCode»). Бандл mirrors/lines/projtimeout2-1142-6053578047.bundle. Посадка: l89 (l88 уже собирается без этого фикса; воркер индексации остаётся выключен до l89).
2026-09-02T06:33:29.926Z · Fable
[02.09 06:50Z Фабл] Ре-приёмка 1144-accprojtimeout2: PASS (6053578047; real PG: transient → retry → APPLIED; transient incident → авто-repair; неизвестный errorCode → OPEN; 5 transient → PENDING_ACTIVE без дебета; semantic effect_mismatch → OPEN + ACCOUNTING_CORRUPTION + paidHealth.ready=false + лог terminal_projection_auto_repair_skipped; ручной acctfix закрывает; hash fdfcf388…b581). Создан l89-candidate (A2) = l88 + 16e326884b + 6053578047; зеркало A1 обновлено. Посадка l89 после l88; воркер индексации включать только с l89.
2026-09-02T21:15:36.928Z · Fable
[A1 21:20 Фабл] Волна 1365-web467projtimeout поставлена (A1, Sol medium): транзиентные ошибки проекции (таймаут транзакции) → retry, не ACCOUNTING_CORRUPTION; короткая транзакция; blast-radius per-payer вместо 503 всем.
2026-09-02T22:23:50.563Z · Fable
[A1 23:55 Фабл] Волна 1365-web467projtimeout сдана: коммит 16ddd1b303 (транзиент → retry, короткая транзакция проекции, blast-radius per-payer). Отчёт: A1 /home/ubuntu/waves/WEB467PROJTIMEOUT-REPORT.md. Приёмка 1395 (A1, Sol medium, в очереди) — деньги: сомнение = NO-GO.
2026-09-02T23:33:56.041Z · Fable
[A1 04:05 Фабл] Приёмка 1395-accweb467projtimeout = NO-GO (отчёт A1 /home/ubuntu/waves/ACCWEB467PROJTIMEOUT-REPORT.md). PASS: транзиент → retry/идемпотентность, реальная коррупция по-прежнему открывает ACCOUNTING_CORRUPTION, semantic isolation. BLOCKER 1: payer gate fail-open до 5 с после открытия инцидента (положительный кэш closed-gates 5000 мс в spendGuard.ts:510-521 читается payer-check :663-675). BLOCKER 2: 20 конкурентных первых событий нового SERVICE-плательщика → несколько pooled wallets, retry исчерпан (11 ok / 9 rejected). Круг 2 = волна 1414-web467projtimeout2 (A1, Sol medium, от 16ddd1b3): инвалидация кэша при открытии инцидента, get-or-create wallet под уникальным ограничением/advisory lock, нагрузочный тест на реальном PG.
2026-09-03T00:40:20.948Z · Fable
[A1 09:30 Фабл] Волна 1414-web467projtimeout2 сдана: коммит 85cfc67044 (инвалидация кэша gate при открытии инцидента; get-or-create wallet под уникальным ограничением/advisory lock; нагрузочный тест на PG16). Отчёт: A1 /home/ubuntu/waves/WEB467PROJTIMEOUT2-REPORT.md. Приёмка 1437 (A1, Sol medium, в очереди) → l95.
2026-09-03T02:52:03.351Z · Fable
[A1 Фабл] Приёмка 1437-accweb467projtimeout2 = GO (независимая, Sol). Линия projtimeout2 (волна 1414) идёт в l95-candidate поверх базы l95 (после SIP-слияния 1448).
2026-09-03T03:20:26.021Z · Fable
[Фабл] ОПАСНОСТЬ ПОСАДКИ: projtimeout2 (85cfc670) регенерировал projectionImplementationHash.generated.ts от базы l93 и ВЫБРОСИЛ superseded-хэши e4b2e336, 8d41ed45, 250c99ec, f0df5906 — три из них есть в живом списке прода (PROJECTION_LIVE_IMPLEMENTATION_HASHES из receipts). После cherry-pick в l95 модуль без них → receipts осиротеют → paid 503 durable (как 25.08). Плюс миграция web467_pooled_wallet_singleton падает при дубликатах pooled UserBalance — проверяю на проде read-only. Перед сборкой l95 — волна 1438d: регенерация модуля с наследованием списка l94 + тест «живые хэши ⊆ модуль».
2026-09-03T03:20:47.931Z · Fable
[A1 Фабл] Факты прода (read-only): TerminalSpendProjectionReceipt по хэшам — 8d41ed45 ×3282, e4b2e336 ×7, 250c99ec ×1 — все три выброшены из superseded в projtimeout2; при этом текущий l94 не знает 8b7336c8 (×926), fb0ce37f (×690), 0d21a60c (×1) и здоров — значит потеря superseded не гарантирует 503, но риск есть → 1438d регенерирует модуль объединением (l94 ∪ projtimeout2) + тест «receipt-хэши прода ⊆ модуль». Pooled UserBalance: 1 строка, дубликатов нет → миграция web467_pooled_wallet_singleton пройдёт.
2026-09-03T03:48:56.671Z · Fable
[A2 Фабл] Сборка l95 встала на гейте P19 (WEB-093): check-migration-compatibility → «baseline is stale» — миграция web467_pooled_wallet_singleton добавлена без обновления prisma/migration-compatibility-manifest.json; приёмка 1437 гейт не гоняла. Фикс = волна 1438f-l95migbaseline (A1, Luna medium): штатное обновление baseline + доказательство expand-only совместимости с бегущим l94. Правило записано: волна с миграцией обновляет baseline, приёмка миграций гоняет P19.
2026-09-03T05:15:09.291Z · Fable
[A1 Фабл] ПОСАЖЕНО в l95: миграция web467_pooled_wallet_singleton применена 05:08Z (индекс UserBalance_pooled_tenant_key), paid enforce_ready, projection-хэши сохранены (1438d).
2026-09-03T07:17:22.015Z · coordinator
WEB-467 РЕЦИДИВ 03.09 07:07Z на l95 (f7b9a4fd): под нагрузкой (hetzbk-base 07:00 + 3 волны + батарея ролей → load 11 на 4 vCPU) settlement-транзакция (resv_4ab967b1…, openai gpt-5.4-mini, 28923 µUSD) вылетела по interactive-tx timeout 5 с (10.4 с) → epoch 442 CLOSED, SpendSettlementOutbox SETTLEMENT_FAILED + process-local latch → /api/ready/paid 503 всем с 07:08Z. acctfix-repair: 0 инцидентов (это не проекция). Восстановление: ждём expiry резервации (07:21:34Z) → sweep реконсилера settle-at-max → resolveBacklogEntry → reopen. Класс тот же, что 02.09 04:40Z: транзиент ≠ порча, но гейт закрывается всем и не самовосстанавливается. Нужен круг: (1) таймаут interactive-tx settlement ≥ 30 с или разбить транзакцию; (2) авто-ретрай settlement при P2028 «Transaction already closed» до записи в outbox; (3) авто-resolve outbox-строки после успешного sweep-settle той же резервации. Митигировано на A1: drop-ins Nice/CPUWeight для hetzbk-* и wave-dispatcher.
2026-09-03T07:49:02.406Z · coordinator
ВОССТАНОВЛЕНО 03.09 07:53Z. Полный рецепт (l95, paid 503 07:08→07:53Z): (1) снять нагрузку (renice/ionice волн и hetzbk; drop-ins Nice/CPUWeight); (2) дождаться expiry резервации → sweep реконсилера (settle-at-max) — activeDispatched 1→0; (3) resolveBacklogEntry({reservationId, kind:'SETTLEMENT_FAILED', resolvedBy}) из /home/ubuntu/nc (скрипт класть в scripts/, алиас @/ иначе не резолвится; файл переносить scp, heredoc через ssh рвёт TS) → outbox 1→0; (4) acctfix-repair НЕ нужен (инцидентов проекции 0); (5) остались два SpendGateCause category=manual (manual:global, manual:accounting) — reopenAccountingGate их НЕ закрывает (авто-закрытие только для канонических accounting:<24hex>), поэтому reconcile отвечал 'repair refused: still_closed'; штатный путь — setSpendGate(scope,false,reason) для 'accounting' и 'global' ПОСЛЕ проверки backlog.openCount=0 и dispatched=0; (6) systemctl start nc-a1-reconcile → [billing][accounting_gate_reopened], epoch OPEN, /api/ready/paid 200. Ручных правок денежных таблиц НЕ было.
2026-09-03T16:16:10.033Z · coordinator
03.09 21:45 IST. Круг 2 роздан: волна 1579-txtimeout2 идёт на A1 (старт 16:14:44Z, модель Luna xhigh). Основание — сегодняшний рецидив 07:08–07:53Z: расчёт упал с «Transaction API error: Transaction already closed … timeout 5000 ms, however 10456 ms passed», epoch учёта закрылся, SpendSettlementOutbox SETTLEMENT_FAILED OPEN, /api/ready/paid = 503 ВСЕМ 45 минут. Самовосстановления нет: оператор открывал гейт руками в четыре шага, причём reopenAccountingGate() НЕ закрывает причины category=manual (manual:global, manual:accounting) — его авто-очистка берёт только канонические causeKey вида ^accounting:[0-9a-f]{24}$, поэтому реконсилер отвечал «repair refused: still_closed», и пришлось звать setSpendGate('accounting', false) и setSpendGate('global', false). В брифе четыре требования: убрать потолок 5 с на пути расчёта (поднять или разбить транзакцию, с обоснованием), ретрай на P2028 ДО записи в outbox, самовосстановление гейта без оператора после опустошения backlog, и сохранение fail-closed для настоящей порчи учёта с отдельным тестом на разграничение.
2026-09-03T17:53:18.717Z · coordinator
Круг 2 сдан: коммит b26c9684 «fix(billing): recover transient settlement timeouts». Приёмка = волна 1584-accpaidgate на A1: потолок интерактивной транзакции, ретрай на P2028 ДО записи в outbox, автозакрытие причин category=manual (сегодня их пришлось гасить руками через setSpendGate, потому что reopenAccountingGate закрывает только канонические accounting:<24hex>).
2026-09-03T19:23:21.155Z · coordinator
Приёмка 1584 = NO-GO. Требуется узко доказуемая миграционно-реконсиляционная обработка старых manual causes от P2028 при пустом бэклоге: сегодня из-за их отсутствия платный контур стоял 45 минут и открывался вручную через setSpendGate. Круг 3 = волна 1594-paidgate2 на A1.
2026-09-03T22:36:19.679Z · coordinator
Круг 2 сдан в составе совместного коммита e4d8fdbe. Приёмка = волна 1606-accpaidgate3 на A1. Проверяется: потолок interactive-транзакции поднят или транзакция разбита, ретрай на P2028 ДО записи в outbox, транзиент восстанавливается сам. Контекст восстановления 03.09: потребовалось resolveBacklogEntry ПЛЮС setSpendGate для accounting и global, потому что reopenAccountingGate не закрывает причины category=manual.
2026-09-03T22:59:33.105Z · coordinator
Приёмка 1606 = GO для e4d8fdbe. Потолок settlement-транзакции поднят с 5 с (Prisma default) до SETTLEMENT_TRANSACTION_TIMEOUT_MS=30000; ретрай на три попытки стоит вокруг runSettlementTransaction ДО внешнего catch, одиночный P2028 больше не пишет outbox; исчерпанный P2028 пишется как SETTLEMENT_FAILED с category=transient-settlement (не manual), семантический AccountingCorruptionError не ретраится. Посадка волной 1613 (l98merge) на A1. ВНИМАНИЕ координатору: линия вводит новую env SETTLEMENT_TRANSACTION_TIMEOUT_MS — волна обязана сказать, нужна ли она в серверном env прода.
2026-09-05T04:04:32.698Z · coordinator
[05.09 04:04Z координатор] ИНЦИДЕНТ 05.09 03:38–04:13Z (третий случай класса): весь платный AI на проде отказывал «AI spending accounting is being repaired». /api/ready/paid = unready/durable-unhealthy, failures accounting_truth «unresolved settlements at startup: 1 accounting outbox row(s)». Строка SpendSettlementOutbox sob_2b69824…, kind=SETTLEMENT_FAILED, errorCode «Transaction already closed… timeout 5000 ms, 9944 ms passed» (03:32:16Z, под индексацией 11-МБ PDF матрицы движка). Резервация resv_f2fed5d2… уже SETTLED_MAX — денег не потеряно, но гейт закрыт до оператора; reconcile-крон (rc=200 каждые 5 мин) SETTLEMENT_FAILED не закрывает. Лечение: resolveBacklogEntry (settlementLatch.ts:1777) с resolvedBy fable-coordinator-20260905:…, затем /api/cron/spend-reconcile → accountingRepair.reopened=true; paid ok на 3010/3012/edge 04:13Z. Прецеденты: 02.09 web461, 03.09 web467-transient-tx-timeout.
KNOWN ISSUES / ТРАБЛШУТИНГ: симптом «AI ничего не отвечает / accounting is being repaired» → проверить ТЕЛО /api/ready/paid (HTTP 200 при unready!) → select * from SpendSettlementOutbox where state='OPEN' + state резервации → если резервация терминальна: resolveBacklogEntry из дерева с node_modules (скрипт в scripts/, импорт ../src/lib/billing/settlementLatch; из /tmp не резолвится) под DATABASE_URL прода, затем spend-reconcile с x-cron-secret из run/flip-cron.secret. Никаких raw UPDATE по money-таблицам. Системный фикс: волна 1933 (самолечение: терминальная резервация + SETTLEMENT_FAILED → авто-RESOLVED в reconcile; interactive tx timeout settlement ≥ 15 с или retry вне tx).
2026-09-05T04:44:05.556Z · coordinator
[05.09 04:44Z координатор] Самолечение (волна 1933 SETTLEMENTAUTOHEAL, A2, opus) = GO по автору, коммит c4c3f811 поверх 6930c683: settlement tx 15 000 мс/maxWait 5 000 (худший случай был 9 944 мс); один ретрай при «Transaction already closed» до записи SETTLEMENT_FAILED (closeReservation идемпотентен); spend-reconcile авто-резолвит OPEN SETTLEMENT_FAILED только при полном наборе денежных фактов и driftUsd=0 (иначе как сегодня — до оператора); худший простой = интервал крона 900 с вместо «пока оператор заметит». Проверка на проде после посадки: spend-reconcile → .settlementAutoHeal/.accountingRepair; наблюдать частоту [billing][settlement_transaction_retry] — если не около нуля, разгружать БД при индексации (авто-heal маскирует следствие). Приёмка 1943-accsettlementautoheal (A2, opus, адверсариальная: двойное списание, негативы DISPATCHED/расхождение/drift/CORRUPTION, идемпотентность).
2026-09-05T08:46:05.995Z · coordinator
[05.09 08:46Z координатор] ПРИЁМКА (ACCSETTLEMENTAUTOHEAL, A2, opus, настоящий PostgreSQL) = GO, коммит c4c3f811 (на l107 cherry-pick чисто, побайтно те же файлы): tx 15 000/5 000 мс; ровно один повтор ДО записи SETTLEMENT_FAILED, двойного списания нет ни при откате, ни при «закоммитилось, но клиент сообщил об истечении» (один TerminalSpendEvent); не-транзиентные ошибки не повторяются; auto-heal только при терминальной резервации с суммой, совпадающем TerminalSpendEvent, проекции APPLIED, нуле открытых инцидентов, сходящихся квитанциях и driftUsd=0, через тот же resolveBacklogEntry (resolvedBy=spend-reconcile:auto-heal:terminal-settled-consistent); ACCOUNTING_CORRUPTION не трогается; регрессий нет. Находки (не блокеры): «восемь фактов» на деле семь (kind===SETTLEMENT_FAILED всегда true в проде — единственный вызов), рекомендация обернуть вызов auto-heal в свой try/catch (settlementAutoHeal.ts:95-143). КАНДИДАТ В l109.
2026-09-05T12:40:28.157Z · coordinator
[05.09 12:40Z координатор] l109 (135103a33) СЕЛ на прод 12:31Z: оба бэкенда, edge ×8, polling ok, paid ok, воркеры на l109; hetzbk-артефакт опубликован, env-line l109, Pi переключается. Самолечение учёта (78a721b5) в живой линии: settlement tx 15 с + один ретрай + авто-резолв безопасных SETTLEMENT_FAILED в spend-reconcile. Наблюдать [billing][settlement_transaction_retry] и settlement_failed_auto_resolved в журнале.
2026-09-06T04:09:59.446Z · coordinator
[06.09 04:09Z координатор] [2026-09-06T04:09Z координатор] ПРОД l113c, большой PDF cmtkm80jo01u8z8vgj446km1x после revive: бюджет операций пройден (новые operationId, эмбеддинги SETTLED_ACTUAL), но источник → failed: Prisma «Transaction already closed… timeout 5000 ms, 5020 ms passed» в $executeRaw при персисте чанков (interactive tx 5 с). Это второй дефект пути больших документов (после бюджета). → Волна 2173-bigpdftxtimeout (A2, luna xhigh, база l113c): долгие операции вне tx, батчи чанков идемпотентно. KNOWN ISSUE: проверка «revive → done» для большого PDF требует обоих фиксов (2171 для txt, 2173 для PDF-персиста).
2026-09-06T07:42:40.299Z · coordinator
[06.09 07:42Z координатор] [2026-09-06T07:42Z координатор] Волна 2173b-bigpdftxtimeoutb (A2, luna xhigh, продолжение salvage): VERDICT=PASS, commit=66e56137 (база l113c) — эмбеддинги/чтение тела до persistence boundary, чанки пишутся идемпотентными multi-row upsert короткими транзакциями. Независимая приёмка 2202-accbigpdftx поставлена (sol xhigh, с реальной PG). GO → l113d → revive PDF на проде как финальная проверка.
2026-09-06T09:09:53.839Z · coordinator
[06.09 09:09Z координатор] [2026-09-06T09:09Z координатор] ПОСАЖЕНО: ПРОД = l113d 4c73d845 (09:03Z, без простоя; оба бэкенда ready, paid enforce_ready, edge 200×4; миграции применены (184 в репо), hetzbk l113d-4c73d845 + env-pack, Pi standby → l113d). В составе (21 коммит над l113c): большой PDF — персист чанков короткими idempotent-транзакциями (WEB-086/496/467), периметр stream-safe (WEB-495/093), WEB-554 отзыв тетради, WEB-540 translation claim, WEB-543 customer guard, WEB-559 binary adapter, WEB-567 issuer authority, WEB-558 facebook attempt, WEB-491 mobile cold-start, WEB-370 пакет телефонии (репо-часть). Откат = l113c 0390afc9. На проде после посадки: revive PDF cmtkm80jo… → running (персист прошёл 5-с барьер — наблюдаю до done); big-test всё ещё упирается в бюджет (identity v4 — волна 2229).
2026-09-06T13:21:13.663Z · coordinator
[06.09 13:21Z координатор] [2026-09-06T13:21Z координатор] ПРОВЕРЕНО НА ПРОДЕ l113g: большой PDF cmtkm80jo01u8z8vgj446km1x — revive (poisoned retry-limit-exhausted → pending, checkpoint 9801/9801 сохранён) → воркер: result success=true chunks=9801 poisoned=0 → status=done, chunkCount=9801, wikiCompile=skipped (unregistered-release-manifest, ожидаемо). Финализация большого документа на проде работает. big-test 1782161239510l4gl4zzur revive → pending (revive-identity v5), наблюдаю.
2026-09-08T11:16:55.968Z · coordinator
[08.09 11:16Z координатор] **08.09 найден смежный дефект того же класса — и он блокировал батарею ролей.**

`POST /api/admin/wallet-grant` отдаёт **HTTP 200** с `ledgerEntryId`, а баланс не меняется: `GET /api/wallet/balance` возвращает `spendableUsd: 0.004224` во ВСЕХ бакетах (cash, subscriptionGrant, promo) сразу после гранта и час спустя. Запись в леджере есть, в проекцию она не доходит. Следствие: все платные вызовы отказывают с 402 (`FAIL C9-6-grounded-answer http=402 expected=200`), середина батареи ролей валится — и это НЕ регрессия фикса цитат, первая и последняя группы проходят с цитатами.

Заведена волна 2782, вердикт GO, вершина `142e97951944`; независимая приёмка — волна 2787. Требование к фиксу отдельно оговорено: успешный ответ, не двигающий деньги, — худшая часть дефекта, неприменимый грант обязан возвращать ошибку, а не 200.
2026-09-10T18:55:03.527Z · coordinator
BOARD-WASH-20260910:WAVE-3332
По поручению владельца 18:43Z назначена исполнительская волна 3332 (billing), Codex gpt-5.6-luna xhigh, A1. Полная история карточки и база l115c 1ad52e16b доставлены, brief-guard и проверка Git-базы пройдены. Задача: проверить существующую сдачу, устранить остатки, передать бандл и доказательства. Финальная приёмка и посадка остаются за координатором. Запуск группы подтверждён в журнале диспетчера.
КАРТА ДОКУМЕНТОВ: ноут /Users/annakorin/nc-ops-scripts/board-wash-20260910/3332-wash-billing-brief.md; A1 /home/wave/waves/inputs/board-wash-20260910/billing-tickets.json; ожидаемый отчёт /home/wave/waves/3332WASHBILLING-REPORT.md. Правила обогащения: WEB-449.
2026-09-10T19:29:07.367Z · coordinator
RECEIVED-3332-BILLING:WEB-467:884e072b
Координатор получил и полностью прочитал отчёт 3332. Бандл сохранён локально; SHA256 162bd24a65df39a2f2e008bef0a93c2c11203103b1328cb8938254bc7fc6992f совпал, git bundle verify PASS, HEAD 884e072b88b1c5f942079a3410f67ce939faf5b9, BASE l115c. Ожидаемый DONE отсутствует; это не потеря отчёта/бандла.
Новый код только по WEB-439, частичная миграция. По отчёту native tests 5/5; production-door test и scoped tsc не завершились. Независимая приёмка и посадка НЕ выполнены. Существующие статусы сохраняются; рекомендация исполнителя не отменяет более свежие доказательства координатора (в частности, закрытие WEB-503).
Отчёт: /home/wave/waves/3332WASHBILLING-REPORT.md
Бандл: /home/wave/waves/3332WASHBILLING.bundle
Локальная копия: /Users/annakorin/nc-ops-scripts/board-wash-20260910/

### ДЛЯ ТИКЕТА WEB-467

- `2026-09-02 04:40Z`: l87 projection worker hit a 5-second expired interactive
  transaction; a transient DB timeout became `ACCOUNTING_CORRUPTION`, an open
  outbox row, and global paid 503. Manual repair restored it.
- `2026-09-02`: 1133 (`16ddd1b`) added transient classification/retry and
  longer projection transaction, but acceptance 1138 found semantic auto-repair
  too broad. 1142 (`605357804`) made auto-repair depend on saved transient
  error class; 1144 accepted transient retry/auto-repair and semantic OPEN.
- `2026-09-02/03`: wave 1365 (`16ddd1b`) exposed payer blast-radius/cache and
  wallet race issues; acceptance 1395 was NO-GO. Wave 1414 (`85cfc670`) fixed
  cache invalidation and pooled-wallet race; acceptance 1437 was GO for l95.
  These historical SHAs are not all ancestors of current BASE; only current
  source and current-BASE ancestry were used below.
- `2026-09-03`: l95 recurred with settlement timeout, closing the gate. Circle
  2 (`b26c9684`, later joint `e4d8fdbe`) raised settlement transaction budget,
  added retry before outbox, classified exhausted transient settlement failures,
  and preserved semantic fail-closed behavior; acceptance 1606 was GO.
- `2026-09-05`: another 9944ms settlement timeout caused a global outage.
  `78a721b58` added 15s/5s settlement settings, one retry before
  `SETTLEMENT_FAILED`, and safe auto-heal only with terminal/money/projection/
  zero-drift proof; acceptance and l109 rollout were recorded as GO.
- `2026-09-06`: the adjacent large-PDF persistence timeout was handled by
  2173/2173b (`66e56137`, then `4c73d845`), with short idempotent chunk
  transactions; l113d/l113g production notes recorded revive/finalization.
  The `2026-09-08` wallet-grant 200-without-balance defect is a new adjacent
  issue and is explicitly outside this assigned ticket/wave.
- Current BASE source facts: `settlementTransaction.ts` has 15s transaction
  timeout and one transient retry; `projectionErrorClassification.ts` keeps
  semantic mismatch non-transient; `settlementAutoHeal.ts`,
  `src/app/api/cron/spend-reconcile/route.ts`, `sourceIndexingWorkerReadiness.ts`
  and `src/app/api/ready/route.ts` retain the background/liveness and recovery
  contracts. Existing focused test files are
  `src/lib/billing/__tests__/web467SettlementTxRetry.test.ts`,
  `web467SettlementAutoHeal.test.ts`,
  `web467SettlementAutoHeal.integration.test.ts`,
  `projectionErrorClassification.test.ts`, and
  `tests/acceptance/web467-extraction-readiness-independent.test.ts`.
- **Критерии → факты → остаток:** source fixes are already in BASE and were
  not duplicated; current production/live PG proof is outside this wave. Next
  executor must verify l115c on both backends and run the independent acceptance
  with current runtime evidence.
2026-09-12T22:17:55.116Z · coordinator
[12.09 22:17Z координатор] МОЙКА 12.09 (3567-wash-g2-telephony-billing): фикс посажен и пережил три рецидива — timeout 30с + retry на P2028 + auto-heal в коде (settlementTransaction/settlementAutoHeal), после 05.09 рецидивов нет. Осталась только финальная независимая приёмка/живая проверка на l115c. Статус не менять до этой проверки.
Остаток: Независимая приёмка/живая проверка на l115c (оба бэкенда) с текущим runtime-доказательством не проведена.; Смежный дефект wallet-grant 200-без-баланса (08.09) — вне этого тикета (WEB-2782/2787).
Отчёт: /Users/milamarty/waves/3567WASH*-REPORT.md (M4), копия nc-ops-scripts/shift-20260912-resume/wash-20260912/collected/. Проверка по исходнику прода l115g (9c8a9762).
2026-09-12T23:18:32.331Z · coordinator
[12.09 23:18Z координатор] ВЗЯТО В РАБОТУ 12.09 23:17Z (владелец: «мойка = брать тикеты в работу»): волна 3587-billing-projection-timeout-acceptance (Claude sonnet, A1): независимая приёмка фикса таймаута проекции офлайн-тестами + список живых проверок. Сдача — бандл + отчёт, комментарий сюда по вердикту.
2026-09-12T23:40:14.565Z · coordinator
[12.09 23:40Z координатор] Волна 3587 (Claude sonnet, A1) — NO-GO не по коду: source-фикс таймаута проекции подтверждён независимо (позитив+негатив node:test на l115g), но остаток карточки — живая проверка settlement на проде — вне волны; координатор снимет метрику/SELECT из отчёта после посадки l115h. Отчёт colB/3587BILLINGTIMEOUT-REPORT.md.
2026-09-13T08:06:04.378Z · coordinator
[13.09 08:06Z координатор] РАЗБОР 13.09 (волна 3634, DeepSeek): Деньги и расчёты уже починили в коде. Раньше, когда серверу становилось тесно и он не успевал за 5 секунд, он закрывал кассу для всех и ждал человека. Теперь он спокойно пробует ещё раз и, если что-то моргнуло, сам себя чинит. Осталось последнее: посмотреть на живом сервере, что так и есть. Проверяем, что касса всё время открыта, что новых «закрытых» чеков из-за тесноты не появилось, и что если что-то мигнуло — оно само починилось за 15 минут без человека. Если за время наблюдения были настоящие покупки и касса ни разу не закрылась — карточку можно закрывать.
Остаток: Живая проверка settlement на l115c (оба бэкенда) с runtime-доказательством не проведена — это и есть содержимое карточки; финальный прогон остаётся за координатором.; Точные имена таблиц/столбцов/enum-литералов Prisma и полей тела /api/ready/paid — сверить по prisma/schema.prisma (дерева прода на этой машине нет).; Проверить, что SETTLEMENT_TRANSACTION_TIMEOUT_MS реально задан в env прода (комментарий 1098), а не дефолт.
Предложение: Read-only: опрос /api/ready/paid (смотреть ТЕЛО, не статус-код) + SQL по SpendSettlementOutbox / TerminalProjectionIncident / TerminalSpendEvent в окно с ненулевым объёмом расчётов. Пороги: 0 OPEN SETTLEMENT_FAILED с transient errorCode старше 15 мин; resolvedBy=spend-reconcile:auto-heal (не оператор); paid ready непрерывно; в журнале [billing][settlement_transaction_retry] и settlement_failed_aut
Материалы: shift-20260912-resume/colM2|colP2/ (RESULT.md — ранбуки и спецификации). Статус не меняю.
2026-09-13T10:45:17.068Z · coordinator
[13.09 10:45Z координатор] Мойка доски, волна 3653. RETURN: фикс посажен (settlement tx 30с/15с + retry на P2028 до outbox + auto-heal settlementAutoHeal), пережил 3 рецидива (02.09/03.09/05.09), после 05.09 рецидивов нет; source-фикс подтверждён независимо 12.09 (3587, node:test на l115g). Осталась финальная живая проверка settlement на l115c (оба бэкенда) с runtime-доказательством — вне прав мойки (прод read-only). Статус не меняю.
2026-09-13T10:51:33.280Z · coordinator
[13.09 10:51Z координатор] Волна 3657 (refs/waves/3657 = 581611dc): независимая проверка на дереве текущей линии. Исправление таймаута транзакции проекции УЖЕ лежит в основе линии — волна не поверила тексту карточки, а восстановила причинную цепочку по исходникам и истории коммитов, добавила собственные тесты на три обязательных поведения и проверила, что денежные сверки не сдвинулись молча.
Две разные транзакции носили один и тот же дефект «пять секунд по умолчанию»: транзакция проекции (фоновый рабочий, бюджет теперь 30 секунд) и транзакция расчёта (горячий путь запроса, бюджет 15 секунд) — именно вторая закрывала платный вход всем 03.09 и 05.09.
Остаток по карточке один: живое наблюдение расчёта на проде в окне нагрузки. Это работа координатора, волнам прод запрещён.
2026-09-13T18:34:43.530Z · coordinator
[13.09 18:34Z координатор] ПОСТ-QA ЛИНИИ l115k — ПРОГОН ПРОБНИКА НА ЖИВОМ ПРОДЕ. Пробник написан волной 3738 (`scripts/postqa/l115k-postqa.mjs`, 52 офлайн-теста, каждая проверка обязана краснеть на сломанном ответе), прогон делал координатор на 127.0.0.1:3010 в режиме только чтения. Результат `/tmp/l115k-postqa-readonly.json` на A1.

ИТОГ ПО 18 ТИКЕТАМ ЛИНИИ: **PROVEN 0, NOT-PROVEN 2, NOT-PROBEABLE-ON-PROD 16.**

Это честный, а не удобный результат, и он важнее, чем список галочек: **HTTP-проба оказалась неподходящим инструментом для большинства этих тикетов.**
- Клиентские фиксы без серверного обращения: WEB-571 (Web Share/буфер + всплывающее сообщение — целиком в DOM), WEB-576 (маркер происхождения пишется в браузере через JSZip, не серверным маршрутом), WEB-583 (Server Action без стабильного HTTP-контракта + поведение CodeMirror), WEB-651 (нужен уже существующий документ сверх предела и живой вход в комнату).
- Сборочные и операционные: WEB-057 (проверка типов при сборке), WEB-309, WEB-472, WEB-489 (скрипты подготовки хостов, нужен root), WEB-641/644/647/652/657 (комплект бас-фактора и фабрика линии — офлайн-инструменты).
- Небезопасно проверять на проде: WEB-573 (нужно искусственно уронить чтение статуса на общей боевой базе), WEB-575 (каждый живой триггер — либо платный вызов, либо разрушительное удаление по GDPR), WEB-325 (рубильник Gate E — включать на проде запрещено).

ДВА НАБЛЮДЕНИЯ NOT-PROVEN (проверка возможна, но доказательства не получилось):
1. **WEB-467** — `GET /api/wallet/ledger` отвечает 200, но читаемого массива `entries` в ответе нет (`entries: null`). Либо форма ответа не та, что ожидал пробник, либо маршрут изменился. Требует разбора: это может быть и дефект пробника, и дефект маршрута.
2. **WEB-561** — на публичной странице `/public/sec057-fixture-20c9e9b6cdc24c0ea770` нет маркера раскрытия ИИ (ни testid, ни текста). Координатор проверил сам: страница отдаёт 200, 420 КБ, и единственное слово Disclosure в ней — внутри словаря переводов для PDF фактчека, то есть к раскрытию на странице отношения не имеет. **Но вывод «нарушение» делать рано:** раскрытие обязано появляться там, где есть ИИ-содержимое, а это фикстурная тетрадь `sec057-fixture-*`, и в ней ИИ-содержимого может не быть вовсе. Проверка на такой тетради не доказывает ни наличия дефекта, ни его отсутствия. Нужен повтор на публичной странице с заведомо ИИ-сгенерированным содержимым.

ЧТО ИЗ ЭТОГО СЛЕДУЕТ. Ни один тикет линии по результатам этого прогона не закрывается. Закрывать по HTTP-пробе там, где фикс живёт в браузере или в сборке, значило бы поставить галочку без доказательства. Нужны два других инструмента: браузерная проба под входом (для WEB-571/583/651/576) и проверка артефактов сборки (для WEB-057 и операционных). Ставлю это следующим шагом.

Уже доказано на проде независимо от пробника и остаётся в силе: готовность обеих копий с коммитом линии, платный запрос со списанием по факту (`settlement=actual`, 0.001021, 10 кандидатов корпуса), отрисовка публичной страницы, миграции 206→209 с распиской.
2026-09-13T19:44:52.783Z · coordinator
[13.09 19:44Z координатор] ПОПРАВКА: наблюдение «NOT-PROVEN» из прогона пробника пост-QA было ЛОЖНОЙ ТРЕВОГОЙ моего инструмента, а не дефектом маршрута. Проверил руками.

Пробник (волна 3738) ожидал в ответе `GET /api/wallet/ledger` массив `entries` и, не найдя его, записал «не доказано». Настоящая форма ответа другая: `mode`, `contractVersion`, `creditsPerUsd`, `days`, `summary`, `nextCursor`. Маршрут отвечает 200 и данные отдаёт — ошибка была в моей проверке.

ЧТО ВИДНО НА ЖИВОМ ПРОДЕ l115k (сессия фикстурной учётки, только чтение):
- `mode = real_spend_ledger`, `contractVersion = 1`, `creditsPerUsd = 100`;
- сводка за 7 дней: `web_rag_chat` 4225 кредитов / 1595 операций / доля 98.12%, `council_stage3` 25.32 / 55 / 0.59% и далее;
- день 13.09: `totalCredits 0.1`, **`pendingCredits 0`**, группа `web_rag_chat::gpt-5.4-mini`, `count 1`, `settledCredits 0.1`, статусы **`SETTLED_ACTUAL: 1`**, резервация `resv_a136f5b4b1ba4bceb696d076a146303c`, провайдер openai.

Это ровно та единственная платная проба, которую я прогнал на l115k в 17:51Z, и она записана в журнал как **урегулированная по факту, с нулём подвисших кредитов**. То есть путь проекции и урегулирования на новой линии работает: транзакция уложилась в бюджет, запись не осталась в pending.

ЧТО ЭТО ЗНАЧИТ ДЛЯ ТИКЕТА: наблюдение на проде положительное, но одной операции мало, чтобы закрывать тикет про БЮДЖЕТ транзакции — он про поведение под нагрузкой и на границе таймаута, а не про один успешный запрос. Оставляю открытым; закрытие — после прогона с несколькими одновременными платными операциями (это отдельный шаг лестницы Enterprise-2, где платный путь идёт через provider stub на настоящем шве).

ОТДЕЛЬНО — дефект инструмента: проверку WEB-467 в `scripts/postqa/l115k-postqa.mjs` надо править (ожидает `entries`, должна читать `days`/`summary`). Записываю как долг пробника, чтобы следующий прогон не давал ложных тревог.
2026-09-14T12:07:33.900Z · coordinator
[14.09 12:07Z координатор] WEB-467 — таймаут транзакции в проекции биллинга; и ложная тревога, которую он породил
Обновление от 2026-09-14. Затронутые посадки: l115k (ac4eb8c68d70de829608bcae5a7acef693ab85fb, 13.09 17:27:44Z) — фикс уже был в основе линии; l115l (1628b697d9b20a4446b9bdba20ada0a6c2820d11, 13.09 20:38:15Z) — посажен исправленный пробник.
Карточка написана для человека, который открывает её впервые и не имеет ни журнала смены, ни переписки.

1. ЧТО БЫЛО СЛОМАНО

Под нагрузкой денежная проекция не успевала уложиться в отведённое время транзакции, и это грозило подвисшими кредитами: списание есть, а урегулирования нет. Для пользователя это выглядело бы как «деньги списали, а операция не завершилась».

2. КАК НАШЛИ И ПОЧЕМУ НЕ ПОЙМАЛИ РАНЬШЕ

Волна 3657 (`refs/waves/3657` = `581611dc`) взяла тикет в работу и обнаружила, что фикс таймаута транзакции проекции УЖЕ находится в основе линии. Волна независимо восстановила причинную цепочку, добавила свои тесты и проверила, что денежные сверки не сдвинулись.
Ключевая находка, объясняющая, почему тикет долго не закрывался: ДВА РАЗНЫХ МЕСТА несли дефект «5 секунд по умолчанию» — проекция (фоновый путь, теперь 30 секунд) и расчёт (горячий путь, 15 секунд). Починка одного места не закрывала вопрос.

3. ЭВОЛЮЦИЯ, ВКЛЮЧАЯ ТУПИКИ

3.1. ТУПИК — ЛОЖНАЯ ТРЕВОГА КООРДИНАТОРА. Это главное, что должен знать следующий агент про этот тикет.
В пост-QA линии l115k координатор объявил проблему по WEB-467. Она оказалась ДЕФЕКТОМ СОБСТВЕННОГО ИНСТРУМЕНТА, А НЕ МАРШРУТА.
Пробник ждал в ответе маршрута кошелька массив `entries`. НАСТОЯЩАЯ ФОРМА ОТВЕТА ДРУГАЯ: `mode`, `contractVersion`, `creditsPerUsd`, `days`, `summary`, `nextCursor`. Никакого `entries` в ответе нет и не было.
Проверено РУКАМИ на живом проде:
  `mode = real_spend_ledger`;
  сводка за 7 дней: группа `web_rag_chat` — 4225 кредитов, 1595 операций, 98.12%;
  за 13.09: всего кредитов 0.1, ПОДВИСШИХ КРЕДИТОВ 0, группа `web_rag_chat::gpt-5.4-mini`, количество 1, урегулировано 0.1, статусы `SETTLED_ACTUAL: 1`, резервация `resv_a136f5b4b1ba4bceb696d076a146303c`.
Это была собственная платная проба координатора, записанная как урегулированная ПО ФАКТУ с нулём подвисших кредитов.
Вывод, записанный тогда же: неверное допущение ИНСТРУМЕНТА о форме ответа выглядело как дефект ПРОДУКТА. За ту же смену координатор ошибся в обе стороны — здесь поднял ложную тревогу, а по четырём другим тикетам принял частичную проверку за полную. Оба раза поймала независимая приёмка.

3.2. ТИКЕТ НЕ БЫЛ ЗАКРЫТ, И ЭТО СОЗНАТЕЛЬНО. Он про БЮДЖЕТ ТРАНЗАКЦИИ ПОД НАГРУЗКОЙ, а одной операции для этого мало. Поправка записана на доску, долг пробника зафиксирован.

3.3. ДОЛГ ПРОБНИКА ЗАКРЫТ. Проверка кошелька переписана под настоящую форму ответа (`mode='real_spend_ledger'`, путь `days[].groups[].details[]`) и теперь смотрит на СТАТУСЫ УРЕГУЛИРОВАНИЯ и ПОДВИСШИЕ КРЕДИТЫ, а не на несуществующее поле. Сдача 3742 = `c5ba6af66`.

4. ЧТО СДЕЛАЛИ В ИТОГЕ

  Продуктовый фикс таймаутов уже находился в основе линии l115k (`ac4eb8c68d70de829608bcae5a7acef693ab85fb`, посажена 13.09 17:27:44Z); волна 3657 это подтвердила и добавила свои тесты.
  Исправленный пробник пост-QA (3738, `cc72a18f9`; доработка проверки кошелька — 3742, `c5ba6af66`) вошёл в линию l115l (`1628b697d9b20a4446b9bdba20ada0a6c2820d11`, посажена 13.09 20:38:15Z).
  План живого наблюдения расчёта и КРИТЕРИЙ ЗАКРЫТИЯ написаны отдельно (волна 3711) и применены на доску.

5. ЧЕМ ДОКАЗАНО И ГРАНИЦЫ ЗАЯВЛЕНИЯ

ДОКАЗАНО:
  Фикс присутствует в линии и денежные сверки не сдвинулись — проверено волной 3657 с собственными тестами.
  ОДНА живая платная операция на проде прошла и была урегулирована по факту: статус `SETTLED_ACTUAL`, подвисших кредитов 0 (числа выше).
  Отдельно, для контекста платного пути: платные пробы после каждой из трёх посадок дали `settlement=actual`. l115k — `costUsd 0.001021`, операция `op_63d882b5a67fb7c905e6f1f41f5b6184`, расписка `candidatesConsidered: 10, candidatesDropped: 0`. l115l — `costUsd 0.001017`, 10 кандидатов корпуса, 0 отброшено. l115m — `costUsd 0.001017`, операция `op_2e20b045862b3a67cfd598debec8eb3d`, модель `gpt-5.4-mini-2026-03-17`, `usedUnknownModelFallback = False`.

ГРАНИЦЫ — читать обязательно:
  ПОВЕДЕНИЕ ПОД НАГРУЗКОЙ НЕ ДОКАЗАНО. Одна операция — не нагрузка, и ровно поэтому тикет не закрыт.
  Тревога, поднятая по этому тикету в пост-QA l115k, была ЛОЖНОЙ. Если в истории встретится тот сигнал — он отменён.

6. ЧТО ОСТАЛОСЬ ОТКРЫТЫМ

  Живое наблюдение расчёта под нагрузкой на проде по критерию, который уже написан (волна 3711).

7. TROUBLESHOOTER — ЕСЛИ КРЕДИТЫ ПОДВИСАЮТ

Шаг 1. НЕ ИСКАТЬ В ОТВЕТЕ МАРШРУТА КОШЕЛЬКА ПОЛЕ `entries` — ЕГО НЕТ. Настоящая форма: `mode`, `contractVersion`, `creditsPerUsd`, `days`, `summary`, `nextCursor`. Инструмент, ожидающий `entries`, поднимет ложную тревогу — это уже происходило.

Шаг 2. Смотреть по пути `days[].groups[].details[]` на СТАТУСЫ УРЕГУЛИРОВАНИЯ и на ПОДВИСШИЕ КРЕДИТЫ. Если подвисших ноль и статусы `SETTLED_ACTUAL` — денежный путь исправен, и проблема в другом месте (например, в самом инструменте проверки).

Шаг 3. Если таймаут действительно происходит — помнить, что мест ДВА и они разные: проекция (фоновый путь, бюджет 30 секунд) и расчёт (горячий путь, бюджет 15 секунд). Починка одного не закрывает вопрос.

Шаг 4. Для проверки платного пути целиком есть пробник посадки. Он ОСТАНАВЛИВАЕТСЯ, если хоть одно поле не совпало; ожидаемый результат — `billing.status=billed` и `settlement=actual`. Если пробник переносится на новую линию, обязательно проверять остатки: в нём три константы (каталог, контрольная сумма, исходный коммит), и после переноса ни одного упоминания старой линии остаться не должно.
2026-09-14T14:38:13.452Z · coordinator
[14.09 14:38Z координатор] VERDICT=GO

# WEB-467 — транзиентный таймаут транзакции проекции (5 с) под нагрузкой

Волна 3861 · base `refs/waves/l115n` (d85bb2dad) · ветка `wave/3861-web467-projection-timeout`

---

## 0. Резюме (коротко)

Тикет прав: под нагрузкой «транзакция проекции» действительно умирала по бюджету **5 секунд**.
Но 5 секунд — это **не константа в репозитории**, а **дефолт Prisma** для интерактивной транзакции
`prisma.$transaction(fn)` без второго аргумента. Обе транзакции, в которых живёт проекция,
раньше вызывались без опций и наследовали этот дефолт:

| транзакция | где была (до фикса) | бюджет тогда | бюджет теперь |
|---|---|---|---|
| терминальная проекция `projectTerminalSpendEvent` | `src/lib/billing/terminalEventProjection.ts:279` (родитель a388a4758) | 5 000 мс (дефолт) | **30 000 мс** (`terminalEventProjection.ts:64`) |
| расчёт/закрытие `closeReservation` (внутри — сканы проекции) | `src/lib/billing/spendReservation.ts:1536` (родитель 78a721b58) | 5 000 мс (дефолт) | **15 000 мс** (`settlementTransaction.ts:42`) |

Я **воспроизвёл** таймаут на одноразовой PostgreSQL 16.14 + Prisma 5.22.0 (версия репозитория):
дефолтная транзакция умирает с `P2028` («timeout … 5000 ms, however … ms passed») и **откатывается
без единой записанной строки**. Та же самая нагрузка под новым бюджетом (15 с) — **проходит**.
Доказательство и числа — в §3 и `3861WEB467PR-evidence/repro/repro.out`.

Главная граница (чего фикс НЕ покрыл): явный бюджет получили **только две** транзакции. Из **23**
вызовов `prisma.$transaction` в `src/lib/billing/*.ts` ещё **21** идёт на дефолте 5 с — включая две
прямо в домене проекции: claim событий (`terminalEventProjection.ts:694`) и повторная постановка
(`settlementLatch.ts:700`). Общий `withRetry` кошелька не ловит `P2028`. Подробно — §4/§5/KNOWN ISSUES.

---

## 1. Сама транзакция и её бюджет: файл, строка, значение, откуда 5 секунд

### 1.1 Где живёт проекция

Проекция терминального события списания — это `projectTerminalSpendEvent`, асинхронный воркер,
который settlement запускает **после** коммита неизменяемого события
(`src/lib/billing/spendReservation.ts:2292` будит `projectTerminalSpendEvent(result.terminalEventId)`).

Внутри него интерактивная транзакция — `src/lib/billing/terminalEventProjection.ts:309-316`:

```ts
return await prisma.$transaction(
    async (tx) => projectTerminalSpendEventTx(tx as ProjectionTx, event),
    { maxWait: TERMINAL_PROJECTION_TRANSACTION_MAX_WAIT_MS,   // 2 000 мс
      timeout: transactionTimeoutMs },                        // 30 000 мс
);
```

Константы — `src/lib/billing/terminalEventProjection.ts:64-66`:
`TERMINAL_PROJECTION_TRANSACTION_TIMEOUT_MS = 30_000`, `…MAX_WAIT_MS = 2_000`, `…MAX_ATTEMPTS = 5`.

### 1.2 Где был бюджет 5 секунд

В родителе коммита `a388a4758` (2026-09-02, «fix(billing): retry transient terminal projection failures»)
эта же строка была (`terminalEventProjection.ts:279` в том дереве):

```ts
return await prisma.$transaction(async (tx) => projectTerminalSpendEventTx(tx as ProjectionTx, terminalEventId));
```

**Без второго аргумента.** Значит таймаут был дефолтным.

Второй виновник инцидента — транзакция расчёта `closeReservation`
(`src/lib/billing/spendReservation.ts:1655`, сейчас `}, SETTLEMENT_TRANSACTION_OPTIONS)` на `:2239`).
В родителе `78a721b58` (2026-09-05) она была `prisma.$transaction(async (tx) => {…})` без опций
(`spendReservation.ts:1536` в том дереве) — тот же дефолт 5 с.

### 1.3 Откуда берётся 5 секунд

**Из Prisma, а не из репозитория.** `prisma.$transaction(fn)` без объекта опций использует
`timeout: 5000` (и `maxWait: 2000`). В коде это не видно и `grep`ом не находится — именно поэтому
его не поймали раньше (см. «ДЛЯ ТИКЕТА»).

Я это **доказал эмпирически**, а не со слов документации: в воспроизведении §3 транзакция без опций
сама отвечает дословно:

> Transaction API error: Transaction already closed: A query cannot be executed on an expired
> transaction. **The timeout for this transaction was 5000 ms**, however 6002 ms passed since the
> start of the transaction.

### 1.4 Инцидент, к которому это относится

`src/lib/billing/settlementAutoHeal.ts:4-17` (модульный комментарий) и
`src/lib/billing/settlementTransaction.ts:4-16`, плюс сообщение коммита `78a721b58`:

> три раза: 02.09 web461, 03.09, 05.09 03:32Z–04:13Z. Под нагрузкой индексации PDF на 11 МБ
> settlement-транзакция вылезла за бюджет 5 000 мс. Prisma ответила
> «Transaction already closed … 9944 ms passed», транзакция ОТКАТИЛАСЬ, fail-safe закрыл
> accounting-epoch и глобальный гейт, и **каждый платный AI-вызов в продукте отвечал 503
> «AI spending accounting is being repaired» 35 минут**, пока оператор вручную не запустил
> `resolveBacklogEntry`.

Число 9 944 мс — это **заявлено** (из production-лога, цитируется в комментариях кода и runbook),
я его вживую не видел; но форму таймаута я воспроизвёл (§3), включая перелёт «ms passed» за бюджет.

---

## 2. Что внутри транзакции: операции и что из них может ждать

Разбираю по факту, что лежит внутри `closeReservation` (`spendReservation.ts:1655-2242`) — это та,
которая реально упала в проде. Happy-path — **~10-13 round-trip'ов под строчными блокировками**
(комментарий `settlementTransaction.ts:12` называет это «a dozen-plus round trips under a row lock»):

1. `SELECT "provider" FROM "SpendReservation"` — `spendReservation.ts:1657` (пред-чтение).
2. `lockAccountingStateForTransition` — `spendReservation.ts:1663` → `accountingStateMachine.ts:313`.
   Внутри него, по порядку:
   - `SELECT … FROM "SpendAccountingEpoch" … FOR UPDATE` — `:332-335` (глобальный эпохальный замок);
   - `provisionMissingGateRowsTx` (INSERT недостающих гейт-строк) — `:343`;
   - `SELECT … FROM "SpendGate" … FOR UPDATE` — `:348-352` (замок гейта, глобальный + accounting + provider);
   - **скан проекции №1**: `SELECT … FROM "TerminalProjectionIncident" WHERE state IN ('OPEN','REPAIRING') … FOR UPDATE` — `:400-403`;
   - **скан проекции №2**: большой запрос по `TerminalSpendEvent` + `TerminalProjectionIncident` +
     `TerminalSpendProjectionReceipt` («есть ли событие, у которого не хватает receipt'а») `… FOR UPDATE OF e` — `:421-439`.
3. `SELECT … FROM "SpendReservation" … FOR UPDATE` — `spendReservation.ts:1693` (замок строки резервации).
4. (ветка replay, если уже терминал) `SELECT … FROM "TerminalSpendEvent" … FOR UPDATE` — `:1717`.
5. Счётчиковая CTE на каждую область: `WITH locked AS MATERIALIZED (SELECT … FROM "DailySpendCounter" … FOR UPDATE), updated AS (UPDATE …)` — `:1859-…` (до 3 областей: GLOBAL + ACCOUNTING + provider).
6. `INSERT INTO "TerminalSpendEvent"` — `:1931`.
7. `INSERT INTO "TerminalCounterDelta"` на каждую область — `:2042`.
8. (только при breach/fail-safe) `INSERT "SpendSettlementOutbox" / "SpendGate" / "SpendGateCause"` — `:2174/:2199/:2205`.

**Что из этого может ждать (блокироваться):**

- **Все `FOR UPDATE`** — строчные блокировки: эпоха (`SpendAccountingEpoch`), гейт (`SpendGate`),
  резервация (`SpendReservation`), счётчики (`DailySpendCounter`), событие (`TerminalSpendEvent`),
  инциденты (`TerminalProjectionIncident`). Любая параллельная settlement-транзакция, держащая ту же
  строку, заставит нашу ждать. Самая горячая точка — глобальный эпохальный замок: **каждый settlement
  сериализуется на одной строке**.
- **Скан проекции №2** читает `TerminalSpendProjectionReceipt` через `LEFT JOIN` — может ждать
  параллельную запись receipt'ов проекции.
- **Ожидание соединения из пула** — `maxWait` (сейчас 5 000 мс, был дефолт 2 000 мс): под нагрузкой
  первым истекает именно он, а не работа (прямо сказано в `settlementTransaction.ts:24-26`).
- **Внешних вызовов внутри транзакции нет** — только БД и `createHash('sha256')` для id (CPU,
  пренебрежимо). То есть «внешний сетевой вызов» исключён; задержку дают блокировки и нагрузка БД.

Отдельная терминальная проекция (`terminalEventProjection.ts:309`) — это уже **короткая** критическая
секция: неизменяемое событие читается **до** транзакции (`:304-308`, «read hoisted out of the tx»),
внутри — только проверка кошелька/леджера и запись receipt'ов. Именно поэтому ей хватило 30 с.

---

## 3. Воспроизведение таймаута (одноразовая Postgres) — доказано

**Среда:** PostgreSQL 16.14 (Homebrew), Prisma `@prisma/client` + `prisma` **5.22.0** (та же версия,
что в `package.json:201/337`), Node 26.5.0, изолированный кластер на `127.0.0.1:55941`
(`initdb`/`pg_ctl`, без sudo, без внешних сервисов). Скрипт и вывод —
`3861WEB467PR-evidence/repro/repro.mjs` + `repro.out`.

Минимальная схема: `LockRow` (заменитель строки «эпоха/резервация», которую держат `FOR UPDATE`)
и `SlowEvent` (заменитель записи, которую транзакция должна была оставить durable).

| сценарий | что делали | результат (измерено) |
|---|---|---|
| **A — медленная работа, дефолтный бюджет** | `$transaction` без опций, тело `SELECT pg_sleep(6)` + INSERT | `P2028` через **6 009 мс**; «timeout … **5000 ms**, however **6002 ms** passed»; **0 durable строк** |
| **B — удержанный row-lock, дефолтный бюджет (форма инцидента)** | держатель держит строку `FOR UPDATE` + `pg_sleep(8)`; жертва без опций пытается `FOR UPDATE` ту же строку | `P2028`; «5000 ms, however **7503 ms** passed»; **0 durable строк** |
| **C — тот же lock, бюджет WEB-467 `{timeout:15000, maxWait:5000}`** | идентичная блокировка | **SUCCEEDED** через **7 511 мс** (ждала ~7.5 с замок, всё ещё < 15 с); **1 durable строка** |
| **D — «один retry»** | попытка 1 на дефолте 5 с → `P2028`; повтор с `{timeout:15000,maxWait:5000}` | попытка 2 **SUCCEEDED**; **1 durable строка**; `attempts = 2` |

**Выводы, которые даёт воспроизведение:**

1. Бюджет **5 000 мс — дефолт Prisma**, и он исчерпывается и «работой» (A), и «ожиданием замка» (B).
2. При исчерпании транзакция **откатывается** — 0 durable строк в A и B (проверено `count(*)`).
3. **Важный нюанс, который объясняет «9944 ms passed» из прода:** таймаут Prisma — это **мягкий
   клиентский дедлайн, а не серверный `statement_timeout`**. Ошибка всплывает только когда текущий
   statement вернёт управление. В A это конец `pg_sleep` (6 009 мс), в B — момент, когда держатель
   отпустил замок (7 503 мс «passed» при бюджете 5 000). Поэтому в проде при бюджете 5 с в логе стояло
   «9 944 ms passed» — транзакция висела на замке/нагрузке, и дедлайн смог сработать только в конце.
4. Бюджет WEB-467 (15 с + `maxWait` 5 с) **переживает ровно ту нагрузку**, которая убивала дефолт (C),
   а один retry закрывает случай, когда первый заход всё же истёк (D).

Это и есть доказательство того, что: (а) причина названа верно, (б) фикс работает на механике.

---

## 4. Что происходит при исчерпании: откат, повтор, потеря, видит ли пользователь

**Откат.** Интерактивная транзакция Prisma при истечении таймаута **откатывается** — ни одна запись
не durable (доказано в §3: 0 строк).

**Повтор — два разных контура:**

- *Settlement* (`closeReservation`): `withSettlementTransactionRetry` (`settlementTransaction.ts:72-83`)
  повторяет **ровно один раз и только** для формы «Transaction already closed» (`P2028` / текст,
  `isTransactionAlreadyClosedError` `:59-66`). Двойное списание исключено идемпотентностью
  (`ON CONFLICT DO NOTHING`, replay-ветка — `settlementTransaction.ts:33-38`).
- *Проекция* (`projectTerminalSpendEvent`): до 5 попыток с backoff (`terminalEventProjection.ts:66, 322-326`),
  при транзиентной ошибке событие остаётся `PENDING`, счётчик попыток растёт, гейт/инцидент **не** создаётся.

**Потеря.** Денег не теряется ни в одном контуре: settlement либо записался целиком, либо откатился
целиком и повторился; проекция — идемпотентная запись receipt'ов, событие дождётся.

**Видит ли пользователь.** Это зависит от того, **какая** транзакция истекла:

- Истекла **settlement** (случай 05.09): до WEB-467 fail-safe трактовал liveness-сбой как денежный —
  писал `SpendSettlementOutbox` `SETTLEMENT_FAILED`, закрывал эпоху и глобальный гейт, и **пользователь
  видел 503 «AI spending accounting is being repaired» на каждом платном вызове 35 минут**. После WEB-467:
  сначала один retry; если retry тоже упал — тот же fail-safe, но auto-heal на ближайшем
  `spend-reconcile` (интервал ≤ 900 с) сам резолвит строку по правилу `terminal-settled-consistent`
  (только когда все денежные факты сходятся и `driftUsd === 0`).
- Истекла **проекция** (асинхронная): пользователь **не видит ничего** — событие просто остаётся в
  очереди до следующей попытки. Влияние только на отставание проекции.

---

## 5. Три корзины предложений с ожидаемым эффектом числом

> Правило волны 3853: эффект числом, и честно — что измерено, что оценка.

**Корзина 1 — «Бюджет» (уже в дереве, это и есть фикс WEB-467).**
Поднять дефолт 5 с на явные бюджеты: settlement `15 000 мс` + `maxWait 5 000 мс` + один retry
(`settlementTransaction.ts:42-50`); проекция `30 000 мс` + `maxWait 2 000 мс` (`terminalEventProjection.ts:64-65`).
*Эффект числом:* **измерено** — нагрузка, которая убивала транзакцию за 5 с, теперь проходит за ~7.5 с (C)
и при первом истёкшем заходе добивается ретраем (D). Запас по наихудшему заявленному кейсу 9 944 мс →
15 000 мс ≈ **+50 %** (заявлено из прода; моё измерение C/D подтверждает механику, а не сам 9 944 мс).

**Корзина 2 — «Работа внутри транзакции» (НЕ сделано; сократить время удержания замков).**
Сейчас settlement держит строчные замки на **~10-13 round-trip'ах** (§2). Конкретные ходы:
(a) слить счётчиковые CTE всех областей в **один** statement (сейчас до 3 `$queryRaw` на CTE + до 3 на
`TerminalCounterDelta` → 1+1); (b) свернуть два скана проекции (инциденты + недостающие receipt'ы) в один
запрос; (c) убрать дублирующее пред-чтение `SELECT provider` (`spendReservation.ts:1657`) — провайдер уже
есть в reservation-строке. *Эффект числом:* ~12 → ~6-7 round-trip'ов, т.е. **примерно вдвое** меньше
времени под замком. Оценка по наихудшему кейсу: 9 944 мс / 12 ≈ **~830 мс на round-trip** — это **оценка
делением, не измерение**; реальную цену round-trip'а под нагрузкой надо мерить отдельным прогоном (см. §6).
Внимание: вынос скана проекции **за** транзакцию — осознанно отвергнут в коде (это замок, делающий
инвариант истинным; `settlementTransaction.ts:27-30`), предлагаю только слияние/сокращение внутри.

**Корзина 3 — «Контенция у источника» (НЕ сделано; данные волны 3853).**
3853 измерила: на каждый аутентифицированный запрос — **минимум 5 Prisma-запросов до тела роута** плюс
`UPDATE` продления лиза по **одной горячей строке** (`PrismaActivePassiveLeaseStore.renew()`), и горячая
строка сериализует весь admission. Их же `repro3.mjs` (та же PG, тот же Prisma): hot-row `UPDATE`
96 конкурентов ≈ **14.4k upd/s, p99 11 мс** против spread ≈ **24.7k upd/s, p99 3 мс** — то есть
**−38…40 % пропускной способности** из-за одной строки (24 conc: 15 856 vs 25 699; 48 conc: 15 099 vs 24 727 —
числа волны 3853, я их не перемеривал). Убрав/расшарив горячую строку лиза и сократив 5 пред-роутных
запросов, пул получает свободную ёмкость, из-за нехватки которой settlement и улетал за бюджет.

---

## 6. Прогон, который проверит гипотезу живьём, и что её опровергнет

**Гипотеза:** «под нагрузкой вида индексации settlement-транзакция больше не улетает за бюджет, а
транзиентное истечение — самолечится и не закрывает платный продукт».

**Прогон (не прод, не моя машина-продакшн — стенд/одноразовая PG):**

1. Поднять нагрузку-держатель замка: параллельная транзакция держит `SpendAccountingEpoch`/`SpendGate`
   (или тяжелый `pg_sleep`-индекс) на время `T` (например 8 с и 16 с — ниже и выше нового бюджета 15 с).
2. В это время вызвать `closeReservation` для диспатченной резервации.
3. Утверждать: (а) при `T < 15 с` settlement **коммитится** (или первый заход истёк, а единственный retry
   лёг на идемпотентный replay); (б) `TerminalSpendEvent` = `settledMicros`, `TerminalCounterDelta` сходятся,
   `projectionState = 'APPLIED'`; (в) **нет нового** OPEN `SpendSettlementOutbox` `SETTLEMENT_FAILED`;
   (г) `/api/ready/paid` остаётся healthy, 503 не появляется.
4. Кросс-чек проекции: ровно **один** дебет в леджере плательщика — готовый судья уже есть в
   `scripts/postqa/l115k-postqa.mjs` (`judgeWalletLedgerReconciliation`, id `WEB-467`, строка ~389):
   0 дебетов = «проекция потеряла», >1 = «задвоила» — оба и есть провал фикса.

**Что опровергнет гипотезу (конкретный наблюдаемый результат):**

- Держатель замка на `T = 16 с` (выше нового бюджета 15 с): settlement **и** его единственный retry оба
  истекают, и при этом резервация остаётся **не** в терминальном состоянии (`DISPATCHED`, не
  `SETTLED_*`/`RELEASED_*`). Тогда auto-heal по правилу 3 откажет, появится свежий OPEN `SETTLEMENT_FAILED`,
  гейт снова закроется и 503 вернётся. Это опровергает «самолечение при транзиентном истечении».
- Либо в леджере плательщика окажется **0 или 2** дебета за один settlement (судья `judgeWalletLedgerReconciliation`
  вернёт `NOT-PROVEN`) — опровергает «проекция применяется ровно один раз».
- Либо при `T < 15 с` (но > 5 с) settlement всё равно упадёт без retry и закроет гейт — опровергает «бюджет
  достаточен». (Моё воспроизведение C/D говорит, что этого НЕ будет; прогон — проверка на стенде.)

---

## ДЛЯ ТИКЕТА — полный след для нулевого агента

### Симптом словами пользователя

«Внезапно **все платные функции ИИ перестали работать** — любая из них отвечала ошибкой 503
„AI spending accounting is being repaired“. Это длилось 35 минут, потом само/после ручного вмешательства
отпустило. Никаких проблем с балансом у меня не было — деньги на месте, просто всё платное лежало».

### Как нашли и почему не поймали раньше

Бюджет транзакции **не был написан нигде в коде** — это дефолт Prisma (5 000 мс для
`$transaction` без опций). Поэтому его нельзя было найти `grep`ом, и ревью не видело проблемы.
Спусковым крючком стала индексация PDF на 11 МБ на той же БД: settlement-транзакция, которая держит
строчные замки и делает десяток round-trip'ов, стала занимать 9 944 мс вместо обычных миллисекунд и
пересекла невидимый порог 5 000 мс. Prisma откатила транзакцию («Transaction already closed … 9944 ms
passed»), а fail-safe, который обязан ловить **денежную** порчу, получил обычный **liveness-сбой**
(транзакция просто истекла и откатилась — деньги не пострадали) и по инструкции закрыл платный гейт.

Не поймали раньше, потому что: (1) порог невидимый (дефолт, не константа); (2) нагрузочных прогонов
«settlement под индексацией» не было; (3) первый случай (02.09 web461) посчитали разовым, второго
(03.09) не хватило для диагноза, только третий (05.09) с логом «9944 ms» дал полную картину.

### Эволюция с тупиками

1. **02.09** — первый случай (web461). Разовый, не диагностирован.
2. **02.09** — `a388a4758` «retry transient terminal projection failures»: вынос чтения неизменяемого
   события из транзакции, бюджет проекции 5→30 с, retry транзиентных ошибок. Но **settlement-транзакция
   осталась на 5 с** — это тупик: лечили проекцию, а упал settlement.
3. **03.09** — второй случай. Всё ещё нет корневой причины.
4. **05.09 03:32–04:13** — третий случай, «9944 ms passed», диагноз закрыт.
5. **05.09** — `78a721b58` «self-heal a transient settlement failure»: settlement 5→15 с + `maxWait` 2→5 с +
   ровно один retry для «already closed» + auto-heal `spend-reconcile`.
6. **Сознательные НЕ-изменения** (не тупики, а решения с границей): вынос скана проекции из транзакции
   отвергнут (это замок, делающий инвариант истинным); авто-резолв разрешён только по правилу
   `terminal-settled-consistent` при `driftUsd === 0` — всё остальное остаётся на оператора.

### Что сделано в этой волне (со ссылками на файл и строку)

- Найдена транзакция и бюджет: `terminalEventProjection.ts:64/309`, `settlementTransaction.ts:42-50`,
  `spendReservation.ts:1655/2239`; «5 секунд» = дефолт Prisma, подтверждён эмпирически (§3).
- Перечислены операции и что ждёт: `spendReservation.ts:1657/1663/1693/1859/1931/2042`,
  `accountingStateMachine.ts:332/348/400/421`.
- Воспроизведён таймаут и проверен фикс (§3, скрипт в evidence).
- Описано, что видит пользователь (§4) и граница фикса (§5/KNOWN ISSUES).

### Чем доказано и ГДЕ ГРАНИЦА

**Доказано (измерено мной):** дефолт 5 000 мс; откат без durable-строк; форма ошибки `P2028`
«timeout … 5000 ms, however … ms passed»; мягкий клиентский дедлайн (перелёт «ms passed» за бюджет);
бюджет 15 с + retry переживает ровно ту нагрузку, что убивала 5 с.

**Граница (что осталось открытым):** явный бюджет получили **только две** транзакции
(`terminalEventProjection.ts:309` — apply; `spendReservation.ts:1655` — settlement). Из 23 вызовов
`prisma.$transaction` в `src/lib/billing/*.ts` **21** по-прежнему идёт на дефолте 5 с:
`terminalEventProjection.ts:694` (claim событий проекции — **та же** тяжёлая NOT EXISTS/EXISTS-развёртка
по `TerminalSpendProjectionReceipt`, только вне транзакции apply), `settlementLatch.ts:700` (повторная
постановка проекции `persistProjectionTransientRetry`), `settlementLatch.ts:199/351/495/556/774/843/1457/1705/2053`
(реконсиляция/reopen), `walletService.ts:722/810/923/996/1261`, `spendReservation.ts:872/2723`,
`accountingStateMachine.ts:256`, `egressProxy.ts:314`, `llmActionReplay.ts:119`. Общий `withRetry`
кошелька (`walletService.ts:499-529`) ловит `P2034/P2024/P2002`, но **не** `P2028` («already closed»).
Значит: wallet-дебет, claim или повторная постановка проекции, упёршиеся в 5 с под той же нагрузкой,
**не ретраятся** (по P2028) и уходят ошибкой наверх. Это — следующая потенциальная точка такого же
класса; воспроизведением её не закрывал.

### Troubleshooter (как быстро подтвердить/опровергнуть на месте)

1. `grep -nF '$transaction' src/lib/billing/*.ts` — смотри, у каких вызовов **нет** второго аргумента с
   `timeout`. Все такие = дефолт 5 с.
2. В логах ищи `Transaction already closed` / `P2028` / `timeout … 5000 ms`. Есть — это класс WEB-467.
3. Проверь, кто упал: `SpendSettlementOutbox` `SETTLEMENT_FAILED` → settlement; `TerminalSpendEvent`
   `projectionState=PENDING` с растущими `projectionAttempts` → проекция.
4. Если строки `SETTLEMENT_FAILED` OPEN, но деньги сошлись (`settledMicros`, `TerminalSpendEvent`,
   `TerminalCounterDelta`, `driftUsd=0`) — жди/запусти `spend-reconcile`, auto-heal сам резолвит.
5. Если НЕ сошлось — не трогай руками деньги, иди по runbook
   `docs/runbooks/accounting-gate-settlement-failed.md` и `scripts/acctfix-repair-incidents.ts`.

---

## KNOWN ISSUES

1. **Граница фикса (§5):** из 23 вызовов `prisma.$transaction` в `src/lib/billing/*.ts` явный бюджет
   получили только 2; **21** всё ещё на дефолте Prisma 5 с, в т.ч. две в домене проекции
   (`terminalEventProjection.ts:694` claim, `settlementLatch.ts:700` requeue). `withRetry` кошелька не
   ловит `P2028`. Воспроизводил только две «починенные» транзакции, остальные — статический анализ
   вызовов, а не прогон. Это главный открытый риск класса WEB-467.
2. **«9944 ms» и «35 минут»** — числа из production-лога/комментариев кода и runbook; **заявлено**, не
   переизмерено (прода и прод-БД под запретом брифа). Моё воспроизведение подтверждает механику и форму
   ошибки, а не сами 9 944 мс.
3. **«~830 мс/round-trip»** в корзине 2 — оценка делением (9944/12), не измерение.
4. **Числа корзины 3** (hot vs spread `UPDATE`, −38…40 %) — измерения волны 3853 на той же PG/Prisma;
   я их не переизмерял, ссылаюсь как на «измерено 3853».
5. **Не измерено** (для честности): реальная цена одного round-trip'а под индексационной нагрузкой;
   длительность авто-хила в живом прогоне (ожидаемая граница ≤ 900 с = `SPEND_RECONCILE_INTERVAL_SEC`,
   заявлено из кода). Чтобы это получить — нужен прогон из §6 на стенде.
6. **Prisma 5.22.0 / PG 16.14** — точные версии моего прогона; на других версиях Prisma текст ошибки
   может отличаться (у проекта для этого и есть `projectionErrorClassification.ts`, который матчит по
   `code` + `sqlstate` + тексту).
2026-09-14T15:14:58.713Z · coordinator
[14.09 15:14Z координатор] **Приёмка 3866 не состоялась по моей вине, не по вине волны. Переставлена как 3878.**

3866 вынесла `NO-GO` с формулировкой «предмет приёмки не существует на этой машине»: ветки `refs/waves/3861/wave/3861-web467-projection-timeout` в зеркале Neo не было, отчёта автора тоже. Она проверила, что это не сломанный доступ (контрольная волна 3853 в том же зеркале **есть**, а 3861 **нет**), и отказалась подставлять похожую ветку. Это правильное поведение — ровно то, чего бриф и требовал.

Моя ошибка: я собрала бандл 3861 в зеркало **на A1**, а на Neo его не перенесла, и в брифе написала «отчёт в той же ветке», хотя отчёты волн в git не коммитятся вовсе. Исправлено: ветка перенесена (`be539fe4e2`), отчёт автора положен файлом `~/waves/author-reports/3861WEB467PR-REPORT.md`, приёмка переставлена как **3878**.

**При этом 3866 успела сделать полезное на самой линии и это в силе:**
- тесты WEB-467 на BASE зелёные — воспроизвела сама: 5 файлов, 60 тестов, 0 падений;
- тесты не пустые — 5 мутаций, все 5 краснеют (негативный контроль пройден);
- ⛔ **число 9 944 мс — заявлено, не доказано**: это строка прозы в комментарии и захардкоженная строка в фикстурах; ни один тест его не измеряет, ни одного лога с ним в репозитории нет;
- ⛔ **«бюджет 15 000 мс с запасом 50 %» закреплён тавтологией** `assert.equal(15_000, 15_000)` — что бюджета хватает, не проверяет ничто;
- ⛔ **сам таймаут в юнит-тестах не воспроизводится** — ошибка вбрасывается счётчиком; единственное настоящее воспроизведение в репозитории не работает.

Ветка приёмки: `refs/waves/3866/acceptance/3866-web467` = `e58fedf02c`.
2026-09-14T15:16:31.055Z · coordinator
[14.09 15:16Z координатор] # WEB-467 - занято, не обогащалось

По вводной задачи WEB-467 сейчас в работе у волн 3865/3866/3868, поэтому я не готовил enrichment-блок и не предлагаю текст для публикации.

Минимальная отметка, проверенная отсюда: live API при финальной сверке показывает статус `review`; в локальном `web-board.sqlite` по WEB-467 видно 42 комментария, последний `id=5016` от 2026-09-14T14:38:13.452Z.

Ничего на доске не менял.
2026-09-14T15:30:33.070Z · coordinator
[14.09 15:30Z координатор] VERDICT=NO-GO

# 3878 — независимая приёмка волны 3861, WEB-467

## Короткий ответ

Механика, на которой построен фикс, подтверждена на настоящем локальном PostgreSQL и Prisma:

- транзакция без опций после медленной работы получила `P2028`, а записанная строка не сохранилась;
- та же блокировка при бюджете `15_000` ms завершилась с одной durable-строкой;
- один повтор после первого `P2028` завершился с одной durable-строкой;
- отрицательный случай: блокировка дольше `15_000` ms снова дала `P2028` и ноль durable-строк.

Это доказывает механику дефолтного бюджета и границу нового бюджета. Но это не доказательство того,
что реальный `closeReservation` под заявленной нагрузкой укладывается в новый бюджет: приложенного
автором `3861WEB467PR-evidence/repro/repro.mjs` и его вывода на машине нет, а настоящий settlement-
прогон из PostgreSQL не был выполнен. Поэтому `GO` автора я не принимаю.

## Симптом словами пользователя

Платная функция внезапно отвечает `503`: «AI spending accounting is being repaired». Деньги не должны
пропадать, но платный продукт останавливается, потому что временный сбой базы принимается за денежную
поломку.

Это описание симптома взято из отчёта автора и комментариев кода. Production-лог я не открывал:
production и прод-БД запрещены условиями приёмки.

## Как найден материал

Проверил refs в зеркале, не накатывая патч:

- авторский ref указывает на `be539fe4e2a4c22d6200f2ba39725a7389d90c29`;
- его родитель — `d85bb2dad15a3ba52c773b0a2362748009a2c3b9`, то есть заявленный base;
- `git diff base..author` содержит только добавленный `3861WEB467PR-REPORT.md`;
- внешний отчёт `/Users/limamarty/waves/author-reports/3861WEB467PR-REPORT.md` байт-в-байт совпадает
  с файлом отчёта в авторском коммите;
- смежный ref 3853 в зеркале также найден, значит поиск зеркала работает;
- отдельный каталог `3861WEB467PR-evidence` и файлы `repro.mjs`/`repro.out`, на которые ссылается
  автор, отсутствуют.

След: `3878ACC467-evidence/static-checks.out`.

## Почему раньше не поймали

Вызов `prisma.$transaction(fn)` без второго аргумента не показывает бюджет в исходнике приложения.
В текущем коде автор явно передал опции в двух важных местах, но остальные billing-вызовы требуют
отдельной проверки. При ревью одного только числа в константе легко принять тестовую фикстуру за
измерение реального времени базы.

В отчёте автора production-время, длительность инцидента и нагрузка описаны словами и строками
фикстур. Production-лог в материалы не приложен и мною не измерялся. Offered/completed-метрики
нагрузки в предоставленных материалах не найдены: подмены одного на другое доказать нельзя, но и
саму нагрузку доказанной считать нельзя.

## Эволюция с тупиками

1. Исторический фикс `a388a4758` добавил бюджет и retry для терминальной проекции.
2. Исторический фикс `78a721b58` добавил бюджет и retry для settlement, а также auto-heal.
3. Волна 3861 заявляет, что это закрывает временный timeout и не маскирует настоящую денежную ошибку.
4. В приложенном отчёте автор описывает будущий стендовый прогон из отдельного раздела, но результат
   этого прикладного прогона не приложен.
5. Я восстановил минимальный настоящий Prisma/PostgreSQL сценарий самостоятельно. Он подтвердил
   общий механизм, но не заменяет `closeReservation` со всеми его таблицами, lock-порядком и
   денежными инвариантами.

## Что проверено по исходникам

Фактические ссылки в снимке автора:

- `src/lib/billing/settlementTransaction.ts:42` — код задаёт
  `SETTLEMENT_TRANSACTION_TIMEOUT_MS = 15_000`;
- `src/lib/billing/settlementTransaction.ts:45-50` — код задаёт `maxWait = 5_000` и передаёт обе
  опции;
- `src/lib/billing/settlementTransaction.ts:59-65` — `P2028` и сообщения закрытой транзакции
  классифицируются как retryable;
- `src/lib/billing/settlementTransaction.ts:72-81` — retry выполняется второй попыткой, только
  после ошибки, распознанной как закрытая транзакция;
- `src/lib/billing/terminalEventProjection.ts:64-66` — код задаёт projection timeout `30_000`,
  maxWait `2_000` и максимум пять попыток;
- `src/lib/billing/terminalEventProjection.ts:304-315` — событие читается до интерактивной
  транзакции, а затем в `$transaction` передаются явные опции;
- `src/lib/billing/spendReservation.ts:1655-1659` — settlement начинается новым вызовом
  `$transaction` и делает предварительное чтение provider;
- `src/lib/billing/spendReservation.ts:2239` — settlement получает
  `SETTLEMENT_TRANSACTION_OPTIONS`;
- `src/lib/billing/spendReservation.ts:2245-2253` — перед fail-safe вызывается ровно один retry.

Статический поиск дал 25 строк с текстом `$transaction` в `src/lib/billing/*.ts`; две из них —
комментарии, значит найдено 23 вызова. Только два целевых вызова выше явно имеют новые опции;
остальные места не были доказаны нагрузочным прогоном этой приёмки. Полный список — в
`3878ACC467-evidence/static-checks.out`.

## Что я выполнил

### 1. Тесты автора

С существующими локальными зависимостями, без установки пакетов, выполнил целевые unit-тесты:

- `web467SettlementTxRetry.test.ts`;
- `web467SettlementAutoHeal.test.ts`;
- `projectionErrorClassification.test.ts`;
- `terminalEventProjection.test.ts`.

Результат: 58 тестов прошли, 0 упали. Отдельно выполнил
`sourceChunkWriterTransactionTimeout.test.ts`: 2 прошли, 0 упали.
Итого в этих двух прогонах: 60 прошедших тестов, 0 падений.

Логи: `3878ACC467-evidence/unit-tests.out` и `3878ACC467-evidence/source-test.out`.

Важно: это в основном in-memory mocks. В `terminalEventProjection.test.ts` нагрузочная часть
сама задаёт задержку fake-транзакции; stdout показал 60 событий за 325.3 ms при симулированной
задержке 4 ms на round-trip. Это измерение тестового fake, не PostgreSQL и не offered/completed
пропускная способность.

### 2. Настоящий PostgreSQL и Prisma

Для отдельного локального кластера PostgreSQL 16.15 использовал уже имеющийся Prisma Client 5.22.0
и модель `Probe`, эквивалентную строке, которую можно блокировать `FOR UPDATE`. Запуск был на
`127.0.0.1:55987`; кластер после прогона остановлен.

Сценарии и результаты из `3878ACC467-evidence/repro/repro.out`:

| сценарий | измеренный результат |
|---|---|
| default, тело спит 6 s и затем пишет | `P2028`; elapsed 6018 ms; в базе 0 строк |
| default, держатель lock 6 s, жертва ждёт тот же lock | `P2028`; elapsed 5982 ms; в базе 0 строк |
| тот же lock, timeout 15000 ms | успех; elapsed 5981 ms; в базе 1 строка |
| первый default timeout, затем retry с timeout 15000 ms | первый elapsed 5979 ms и `P2028`, retry elapsed 4 ms и успех; в базе 1 строка |
| отрицательный контроль: lock 16 s при timeout 15000 ms | `P2028`; elapsed 15978 ms; в базе 0 строк |

В сообщениях Prisma я сам увидел default `5000 ms` и новый timeout `15000 ms`. Это настоящее
измерение поведения Prisma и rollback. Это не измерение времени `closeReservation`.

Скрипт: `3878ACC467-evidence/repro/repro.mjs`.

### 3. Попытка настоящего прикладного integration-теста

Запустил отдельный PostgreSQL-кластер и попытался поднять схему снимка автора для
`servicePayerProjection.integration.test.ts`. `prisma db push` остановился на отсутствии типа
`vector`; в локальном PostgreSQL нет pgvector. После этого тест закономерно получил отсутствующие
таблицы и завершился с ошибкой. Кластер был остановлен, временная ссылка `node_modules` удалена.

Лог: `3878ACC467-evidence/repro/integration-db-push.out` и
`3878ACC467-evidence/repro/service-payer-integration.out`.

Это не дефект WEB-467 и не зелёный результат. Это честный блокер для прикладной проверки.

## Негативный тест

Негативный контроль был реальным, не mock: держатель PostgreSQL lock жил 16 s, а жертва имела
бюджет 15 s. Получено `P2028`, в том числе текст Prisma с timeout 15000 ms и elapsed 15976 ms,
и 0 durable-строк. Значит новая граница не проглатывает случай, который обязан быть за пределом.

Дополнительная отрицательная защита в unit-тестах: искусственный денежный drift не retry-ится и
не проглатывается. Она прошла, но использует fake database; это проверка классификации, не времени.

## Что доказано

- В текущем исходнике целевые settlement и apply projection получают явные transaction options.
- Prisma 5.22.0 на настоящем PostgreSQL использует 5000 ms для интерактивной транзакции без опций.
- При истечении во время работы или ожидания row lock Prisma возвращает `P2028`; запись откатывается.
- Явный timeout 15000 ms переживает самостоятельно измеренный lock примерно 6 s.
- Повтор новой транзакции после первого `P2028` может завершить запись один раз.
- Lock за пределом нового бюджета действительно ломает прогон.
- Unit-контуры не принимают deterministic monetary drift за transient timeout.

## Где граница и что осталось открытым

Главная граница: я не доказал полный прикладной сценарий `closeReservation` под индексационной
нагрузкой. Причины две: repro автора отсутствует среди материалов, а поднятие полноценной схемы
снимка остановилось на pgvector. Поэтому нельзя честно утверждать, что все операции из
`spendReservation.ts:1655-2239` укладываются в 15000 ms, что их lock-порядок не создаёт другую
контенцию, и что retry приводит именно к корректным terminal event, counter delta, ledger и
paid-readiness результатам.

Также не доказаны в этой приёмке production elapsed и длительность пользовательского 503: нет
доступа к production-логам. Не доказаны offered/completed числа: нет реального load-run с двумя
такими метриками.

Чтобы перевести вердикт в GO, нужен следующий прогон на разрешённом непроизводственном стенде:

1. Поднять изолированный PostgreSQL с pgvector и применить схему именно коммита
   `be539fe4e2a4c22d6200f2ba39725a7389d90c29`.
2. Посеять dispatched reservation, accounting epoch, gates, wallet и необходимые projection rows.
3. В отдельной транзакции взять те же epoch/gate/counter locks, которые использует
   `lockAccountingStateForTransition`, и удерживать их сначала дольше старого default, но меньше
   нового settlement timeout.
4. Вызвать настоящий `settleReservation`/`closeReservation`, а не функцию с fake `$transaction`.
5. Зафиксировать elapsed, offered attempts и completed attempts раздельно; проверить, что после
   commit есть ровно один terminal event, согласованные counter deltas и ledger, а новый
   `SETTLEMENT_FAILED` не появился и paid readiness не закрылся.
6. Повторить с lock дольше нового timeout и проверить ожидаемый fail-safe. Отдельно проверить
   первый timeout + единственный retry на реальной БД.

## Troubleshooter для нулевого агента

1. Если видишь `Transaction already closed` и `P2028`, сначала отличи liveness timeout от денежного
   mismatch; не меняй ledger вручную.
2. Проверь `SpendSettlementOutbox`: `SETTLEMENT_FAILED` относится к settlement, а не к обычной
   асинхронной projection queue.
3. Проверь состояние reservation. Только терминальная reservation с совпадающими денежными фактами
   может быть кандидатом auto-heal; `DISPATCHED` оставь оператору.
4. Сверь terminal event, counter deltas, receipts и ledger drift. Любое расхождение — держать gate
   закрытым и идти по `docs/runbooks/accounting-gate-settlement-failed.md`.
5. Для нового timeout смотри не только константу: нужен реальный lock/load прогон и раздельные
   offered/completed метрики.

## KNOWN ISSUES

1. Авторское evidence, на которое ссылается отчёт, не приложено: доказательством остаётся только
   текст отчёта. Я воспроизвёл эквивалентную Prisma механику сам, но не авторский файл.
2. Полный прикладной settlement-прогон не выполнен из-за отсутствующего pgvector в разрешённом
   локальном PostgreSQL; production и прод-БД запрещены.
3. 23 найденных billing-вызова `$transaction` не получили в этой приёмке независимое runtime-
   покрытие. Целевые два места имеют опции; остальные могут сохранять default timeout.
4. Числа времени из production-истории автора и данные соседней волны не переизмерялись. В отчёт
   не заношу их как доказанные измерения.
5. Измерения unit fake и минимальной `Probe`-схемы нельзя выдавать за throughput реального
   settlement или за offered/completed production load.

## Артефакты

- Проверка refs, исходников и материального состава: `3878ACC467-evidence/static-checks.out`.
- Unit-тесты: `3878ACC467-evidence/unit-tests.out` и `3878ACC467-evidence/source-test.out`.
- Реальный Prisma/PostgreSQL repro: `3878ACC467-evidence/repro/repro.mjs` и `repro.out`.
- Неудачная подготовка полного локального integration-стенда: файлы `integration-*` в
  `3878ACC467-evidence/repro/`.
- Контрольная сумма каждого evidence-файла: `3878ACC467-evidence/SHA256SUMS`.
2026-09-15T01:25:01.699Z · coordinator
[15.09 01:25Z координатор] ## 15.09 01:40Z — ВОШЁЛ В ПОСАДКУ l115o. Требуется пост-QA на проде.

Прод работает на `l115o-68e25d8d` (коммит `68e25d8df5ae8263c5ac5466353631f57a17cfcc`, артефакт `cd2773f65d66b09ec03d39b553041d2814ce09413e72d72b07bc9de89df536c8`) с 00:56Z 15.09. Посадка проверена: `ready=true`, коммит совпал, браузерная проба открыла документ, 0 новых ошибок.

**Работу по этому тикету принесли волны:** 3742.

**Что это значит для этого тикета.** Работа по нему пролежала принятой, но НЕ посаженной — в некоторых случаях неделями. Теперь она на проде. Приёмка волной доказывала, что код правильный в дереве волны; она НЕ доказывала, что фича работает на живом проде. Это разные вещи, и мы на этом уже обжигались.

**Поэтому статус — `review`, а не `done`.** Закрыть тикет имеет право только пост-QA, который проверит поведение на работающем проде и приложит доказательство. До тех пор «сделано» — это заявка.

### Для нулевого агента (тот, кто будет делать пост-QA)

Ты приходишь на этот тикет без нашей истории. Что надо знать:

1. **Проверяй на проде, не на стенде.** Стенд сейчас вообще не поднят — у него не было своего артефакта, он запускался из каталога боевого релиза и заблокировал проверку посадки; пересобирается волной 3948 (WEB-662).
2. **`/api/health` ничего не доказывает** — он проходит и на пустом приложении. Нужно деловое действие: аутентифицированная сессия делает то, про что этот тикет.
3. **Отсутствие ошибок в логе — не доказательство жизни.** Нужен положительный результат, а не тишина.
4. **Числа в обосновании закрытия получай командой в момент закрытия.** Я однажды закрыла тикет числом `4255`, взятым из `pg_stat_user_tables.n_live_tup` — это ОЦЕНКА. Настоящее `count(*)` дало `107 817`. Разница в 25 раз.
5. **Не верь имени волны в теме коммита** — проверяй наличие содержимого (`git cherry`), а не упоминание номера.
6. Запускатель тестов в проекте — `node --test`. Vitest нет.

2026-09-15T11:38:13.425Z · coordinator
[15.09 11:38Z координатор] 
2026-09-15T11:40:23.766Z · coordinator
[15.09 11:40Z координатор] [15.09 11:40Z координатор] Post-QA 3985: VERDICT=REVIEW. Пакет и SHA256 проверены.

На production source l115o фикс присутствует: P2028/`Transaction already closed` получает ровно один retry, повторный transient остаётся fail-safe, auto-heal разрешён только при нулевом денежном расхождении. Независимый disposable test-double 4/4 PASS; paid/accounting health живого production зелёный, открытых failures/incidents/outbox/stuck reservations — 0, успешная paid probe дала один APPLIED terminal effect.

Закрывать пока нельзя по одному точному остатку: после старта l115o сама transient P2028-ветка в production не наблюдалась (`retry=0`, это NOT_OBSERVED). Нужна одна очищенная runtime-квитанция реального P2028 → один retry или безопасный auto-heal без закрытия paid gate и без второго terminal effect. Код повторно аудировать не нужно.
2026-09-15T14:05:04.273Z · coordinator
[15.09 14:05Z координатор] [15.09 14:02Z координатор] Post-QA 4033 принят: VERDICT=DONE, exact current-line handler, 4/4 SHA256. На разрешённом disposable runtime обработчик получил Prisma P2028/Transaction already closed после частичной транзакции, сделал ровно один retry и завершил SETTLED_MAX. Итог: один terminal spend event, четыре совпадающие counter-delta receipt, без duplicate terminal effect, SETTLEMENT_FAILED, gate write, epoch close или held reservation. Fail-closed controls и auto-heal прошли; bounded suite 21/21, provider/network/live monetary mutation = 0. Production natural event остался NOT_OBSERVED, но критерий разрешал isolated test-double при отсутствии естественного события. Остаток закрыт, статус review→done.
2026-09-15T22:04:50.052Z · coordinator
ДЛЯ ТИКЕТА WEB-467 - сводка под нулевого агента (составлено 2026-09-15, read-only)

Симптом. Транзиентный таймаут транзакции проекции (5 с) под нагрузкой закрывал платный гейт всем (paid 503), а не одному пользователю.

Отмена в теле. "Где мы сейчас (13.09)" и "Остаток" в теле требуют живой проверки settlement на l115c (оба бэкенда). Это устарело: закрытие произошло на l115o и другим способом - изолированной runtime-квитанцией. Карточка done, а тело всё ещё отправляет нулевого агента на l115c. Читать тело как руководство к действию нельзя.

Механика (тело, не отменено). SETTLEMENT_TRANSACTION_TIMEOUT_MS 30 000/15 000 мс + retry на P2028 до записи в outbox + auto-heal (settlementAutoHeal, resolvedBy=spend-reconcile:auto-heal). Файлы: src/lib/billing/settlementTransaction.ts, projectionErrorClassification.ts, settlementAutoHeal.ts; тесты web467SettlementTxRetry, web467SettlementAutoHeal.

Хронология. 1133/1142/1144 (проекция) -> 1365/1414 (blast-radius, wallet race) -> l95 рецидив -> 1579/e4d8fdbe (settlement 30 с + retry, приёмка 1606 GO) -> 05.09 рецидив -> 1933/c4c3f811 (15 с/auto-heal) -> l109 (135103a33) -> 2173/2173b (l113d) -> мойки 12.09 (3567), 13.09 (3634).

Отмены и круги приёмки:
- 2026-09-14T15:30:33.070Z - 3878, NO-GO. Механика подтверждена на настоящем локальном PostgreSQL и Prisma: транзакция без опций после медленной работы дала P2028, записанная строка не сохранилась; та же блокировка при бюджете 15_000 мс дала одну durable-строку; один повтор после первого P2028 - одну durable-строку; отрицательный случай (блокировка дольше 15_000 мс) - снова P2028 и ноль durable-строк. Но реальный closeReservation под заявленной нагрузкой не доказан: приложенного автором 3861WEB467PR-evidence/repro/repro.mjs и его вывода на машине нет.
- 2026-09-15T11:40:23.766Z - пост-QA 3985, VERDICT=REVIEW. На l115o фикс присутствует; P2028/Transaction already closed получает ровно один retry; независимый disposable test-double 4/4 PASS; paid/accounting health живого прода зелёный, открытых failures/incidents/outbox/stuck reservations - 0. Остаток: сама transient P2028-ветка в production после старта l115o не наблюдалась (retry=0, NOT_OBSERVED).
- 2026-09-15T14:05:04.273Z - пост-QA 4033 принят, VERDICT=DONE, 4/4 SHA256. На разрешённом disposable runtime обработчик получил Prisma P2028/Transaction already closed после частичной транзакции, сделал ровно один retry и завершил SETTLED_MAX: один terminal spend event, четыре совпадающие counter-delta receipt, без duplicate terminal effect, SETTLEMENT_FAILED, gate write, epoch close и held reservation. Fail-closed контроли и auto-heal прошли; bounded suite 21/21; provider/network/live monetary mutation = 0. Естественное событие в проде осталось NOT_OBSERVED, но критерий разрешал изолированный test-double при отсутствии естественного события.

Первый шаг нулевого агента (2 минуты): открыть карточку и прочитать сначала комментарий 4033, а не тело; проверить, что SETTLEMENT_TRANSACTION_TIMEOUT_MS задан в env текущего прода, и что открытых SETTLEMENT_FAILED старше 15 минут нет. Переоткрывать карточку из-за "NOT_OBSERVED" не нужно - критерий это допускал; но если решите переоткрыть, довод должен быть новым.

Карта документов. Отчёт приёмки 4033 в карточке не приложен полным путём - это пробел; при следующем касании его надо дописать. Известные артефакты: 3861WEB467PR-evidence/repro/ (на машине автора 3861 отсутствует - названо приёмкой 3878).

Отдельно - дефект журнала. Комментарий 2026-09-15T11:38:13.425Z пустой (тело нулевой длины). Он стоит между шаблонным "требуется пост-QA" и настоящим вердиктом 3985 и читается как пропущенный вердикт. Это стоит исправить при следующем касании карточки.
Воркер
не проверен 3332-wash-billing A1 движение в панели: неизвестно
Подключиться и смотреть/перехватить руками. Колесо мыши листает; клавишами — Ctrl-b затем [, выход из прокрутки q. Отсоединиться — Ctrl-b затем d:
Прочитать историю панели без подключения — листается и ищется (/ поиск, q выход), воркеру не помешает:
Обновлён
2026-09-15T14:05:04.784Z