diff --git a/docs/architecture/15-money-path.md b/docs/architecture/15-money-path.md index e6ee35c8..4b0bbe65 100644 --- a/docs/architecture/15-money-path.md +++ b/docs/architecture/15-money-path.md @@ -22,7 +22,7 @@ - **`--ceiling-usd` — КНИЖНЫЙ потолок, не бюджет прогона.** Дословно из флага: «the book USD ceiling in force for THIS RUN ONLY — it OVERRIDES book.yaml `ceilings.book_usd` and is never written back. It caps the book's CUMULATIVE committed+reserved spend, not this run's increment…» (`backend/cmd/tmctl/invocation.go:142`=`the book USD ceiling in force for THIS RUN ONLY`; ⚠-коммент `backend/cmd/tmctl/main.go:261`=`--ceiling-usd is NOT a per-run budget`; многоточие закрывает обрыв цитаты — во флаге дальше стоит «, and must be > 0»). Ратификация — D39.122 п.2(в): пересчёт «пользовательский прирост → абсолют» — обязанность ПЛАТФОРМЫ, вторая денежная ось не заводится. День-потолок флаг НЕ перекрывает (PD-157). Проводка внутри: `Ceilings.BookUSD` в `Reserve` и именование сработавшего потолка в ошибке — `backend/internal/pipeline/stagerun.go:629`=`BookUSD: r.bookCeilingUSD()` и `backend/internal/pipeline/stagerun.go:716`=`overrides the book's ceilings.book_usd` (стоп = `errReserveCeiling`, `backend/internal/pipeline/escalation.go:49`=`var errReserveCeiling`). - **Потолки — wiring, не семантика:** `Ceilings` намеренно исключены из `BriefHash` («Wiring fields (paths, ceilings, db) deliberately excluded», `backend/internal/config/book.go:345`=`Wiring fields (paths, ceilings, db) deliberately excluded`; канон — `backend/internal/config/book.go:369`=`canon := struct {`) ⇒ смена ДЕНЕЖНОГО потолка не двигает ни снапшот, ни ре-билл (D39.110 п.2б). ⚠ Верно ровно про ДЕНЬГИ: потолок СЕГМЕНТАЦИИ `edit_ceiling_out` в снапшот ВХОДИТ и меняет границы чанков (`backend/internal/pipeline/snapshot.go:34`=`EditCeilingOut int`). - **`status --json` отдаёт фигуры платформе:** `committed_usd`, `reserved_usd`, `book_ceiling_usd`, `ceiling_pct` = 100·(committed+reserved)/book_ceiling (`backend/internal/pipeline/status.go:342`=`book_ceiling_usd,omitempty`; арифметика `ceiling_pct` — `backend/internal/pipeline/status.go:949`=`100 * (committed + reserved)`). Дневной фигуры в status нет (PD-157 — `platform/docs/DEFECT_REGISTER.md`, греп `PD-157`). -- **Леджер = НИЖНЯЯ граница; строка 78 снята с трекера 10.09, но её ЖИВОЙ ОСТАТОК несёт строка 377.** ⛔ **Испр. 10.09 — прежняя редакция этого пункта, написанная в тот же день, была НЕВЕРНА, и ошибка моя.** Она утверждала, будто оплаченный `2xx` с нечитаемым телом «прежде в `request_log` не попадал» и что это починил пак оборванных вызовов. Замер: ветвь `BilledDecodeError` писала свою строку с пометкой оценки ещё до пака — на `3f05fab^` это `stagerun.go:629`, введено коммитом `2f91b04` **24.07**, а пак этой ветви не касался вовсе (хитов `Estimated` в его диффе по `stagerun.go` — **0**). И носитель я процитировал ЧУЖОЙ: `backend/internal/pipeline/cutcall.go:133`=`rl := r.baseRequestLog(c.stage, c.chunk` обслуживает `*llm.AttemptCutError` (`backend/internal/pipeline/stagerun.go:741`), а речь шла о `*llm.BilledDecodeError` (`stagerun.go:709`) — это разные классы, и второй до `cutcall.go` не доходит. **Что пак действительно купил:** строку `request_log` за вызов, который оборвали МЫ (прежде он падал в ветвь «ничего не куплено» и получал $0-строку) — вот это `cutcall.go:133`. **Что остаётся правдой:** леджер — нижняя граница, потому что обрыв, за которым ретрай УСПЕЛ, исчезает бесследно (строка **377**). Живой замер `D39.86` — 3 вызова из 14, неизвестность $0.015111 при леджере $0.114378. +- **Леджер = НИЖНЯЯ граница; строка 78 снята с трекера 10.09, но её ЖИВОЙ ОСТАТОК несёт строка 377.** ⛔ **Испр. 10.09 — прежняя редакция этого пункта, написанная в тот же день, была НЕВЕРНА, и ошибка моя.** Она утверждала, будто оплаченный `2xx` с нечитаемым телом «прежде в `request_log` не попадал» и что это починил пак оборванных вызовов. Замер: ветвь `BilledDecodeError` писала свою строку с пометкой оценки ещё до пака — на `3f05fab^` это `stagerun.go:629`, введено коммитом `2f91b04` **24.07**, а пак этой ветви не касался вовсе (хитов `Estimated` в его диффе по `stagerun.go` — **0**). И носитель я процитировал ЧУЖОЙ: `backend/internal/pipeline/cutcall.go:133`=`rl := r.baseRequestLog(c.stage, c.chunk` обслуживает `*llm.AttemptCutError` (`backend/internal/pipeline/stagerun.go:802`=`var cut *llm.AttemptCutError` — ⚠ испр. 16.09: было :741), а речь шла о `*llm.BilledDecodeError` (`stagerun.go:709`) — это разные классы, и второй до `cutcall.go` не доходит. **Что пак действительно купил:** строку `request_log` за вызов, который оборвали МЫ (прежде он падал в ветвь «ничего не куплено» и получал $0-строку) — вот это `cutcall.go:133`. **Что остаётся правдой:** леджер — нижняя граница, потому что обрыв, за которым ретрай УСПЕЛ, исчезает бесследно (строка **377**). Живой замер `D39.86` — 3 вызова из 14, неизвестность $0.015111 при леджере $0.114378. - **ГРАНИЦА ДЕНЕГ ЗА ОБОРВАННЫЙ НАМИ ВЫЗОВ — ФАКТ ДОСТАВКИ, а не статус-строка** (пак строки 360, акт `D39.232`; слово владельца — `D39.230`). Деньги берутся, только когда провайдер подтвердил запрос ответным объектом: `backend/internal/llm/attemptcut.go:221`=`Billable: answered && tr.afterHeaders()`. Наш собственный дедлайн даёт свой класс `attempt_timeout` без ретрая; дедлайн вычисляется ИЗ БЮДЖЕТА вызова, а не назначается (`backend/internal/llm/attemptcut.go:269`=`func (p RetryProfile) deriveDeadline`), так что эскалация, удвоившая `max_tokens`, удваивает и время. Оплаченный обрыв получает чекпойнт, и резюм его НЕ перепокупает. ⛔ Пометка «это оценка» — УСЛОВИЕ слова владельца (`D39.230` п.1) — сегодня в движке живёт ТРЕМЯ носителями (колонка `Estimated` в `request_log` — `backend/internal/store/requestlog.go:41`=`Estimated marks a row whose CostUSD`; пара `estimated_rows`/`estimated_usd` в `status --json` — `backend/internal/pipeline/status.go:911`=`Re-payment projection (spec`; печать оператору — `backend/cmd/tmctl/render.go:402`), но НАРУЖУ не выходит ни одним каналом шва: строка **382**. ⚠ Формулировка испр. 10.09 — прежняя («живёт только в `status --json`») звала следующего чинить пометку, которая уже есть. - **Согласие на пере-оплату — ДВИЖКОВЫЙ гейт**. Прогон, который пере-покупает уже оплаченные юниты дороже порога, ОСТАНАВЛИВАЕТСЯ и требует явного согласия. Порог по умолчанию ратифицирован D20.2-Q2: `min($0.50, 5% × ProjectedBookUSD)`. Носитель — `backend/internal/pipeline/rebill.go`, греп `rebillConsentThreshold`; единственная ручка книги — `rebill_consent_usd` (валидатор `backend/internal/config/book.go`, греп `rebill_consent_usd`; 0 = ратифицированный дефолт). ⚠ Порог считается БЕЗ контура банк-ролей (D39.182 §3), то есть срабатывает позже, чем подсказывает полная проекция.