From 824c1e9be98a181b39143d0ba4eb0d0a7b7ba868 Mon Sep 17 00:00:00 2001 From: heaven Date: Tue, 8 Sep 2026 17:05:28 +0300 Subject: [PATCH] Record the converged transport design and the double error that held the wrong deadline: the envelope was derived for a draft and the provider does not hold headers --- docs/BACKLOG.md | 6 ++++-- docs/research/21-llm-transport-survey.md | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 8cb0c28d..2ed0270a 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -3,7 +3,7 @@ > **Единственный трекер проекта.** Здесь живут строки, на которые доки, промты и D-ноты ссылаются словами «строка N» / «строка бэклога N»: **ID строки стабилен навсегда**, не перенумеровывается и не переиспользуется (D39.80). Правки — только через оркестратора; каждая петля обязана иметь диспозицию: решено / отложено-с-записью / отклонено. > ⚠ **Это НЕ бэклог зоны `docs/`, а бэклог ПРОЕКТА.** У зон свои, с другими неймспейсами ID, и единый их строк не принимает (D39.84): платформа — [../platform/BACKLOG.md](../platform/BACKLOG.md) (`П-N`) и её регистр дефектов `platform/docs/DEFECT_REGISTER.md` (`PD-N`); фронт — [../frontend/docs/BACKLOG.md](../frontend/docs/BACKLOG.md) (`Ф-N`). > ⚠ **Состояние проекта — не здесь.** Очередь, курс, CURRENT-STATE, состояние паков и живая хроника — шапка [PROGRESS.md](PROGRESS.md). Здесь только долг и его диспозиции. -> - **СЧЁТ ОЧЕРЕДИ на 08.09 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **260** строк · зона бэкенд **122** строго / **169** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 1** (236 закрыта D39.175), платные прогоны разблокированы · «скоро» **116** (перечень — грепом по таблице, рукописный список снят D39.126) · гейт-строка эксп-22: 153 (плюс 150 — руки владельца; ⚠ 55 из гейтов СНЯТА — D39.198 п.5); строка 5 — остаток гейчен ре-пробой 74, носитель события теперь строка 188 (аудит D39.140); остальное «когда-нибудь». ⚠ Счёт — НИЖНЯЯ граница долга, не потолок (разбор — легенда таблицы ниже). +> - **СЧЁТ ОЧЕРЕДИ на 08.09 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **262** строк · зона бэкенд **124** строго / **171** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 1** (236 закрыта D39.175), платные прогоны разблокированы · «скоро» **118** (перечень — грепом по таблице, рукописный список снят D39.126) · гейт-строка эксп-22: 153 (плюс 150 — руки владельца; ⚠ 55 из гейтов СНЯТА — D39.198 п.5); строка 5 — остаток гейчен ре-пробой 74, носитель события теперь строка 188 (аудит D39.140); остальное «когда-нибудь». ⚠ Счёт — НИЖНЯЯ граница долга, не потолок (разбор — легенда таблицы ниже). > - ⚠ **Переезд 06.09 (D39.218):** таблица вынесена из `PROGRESS.md` ДОСЛОВНО и ни один ID не сдвинут — **с единственным исключением: две ячейки (строки 220 и 306) тронуты РОВНО в номере якоря `docs/scripts/counts.py:566`→`:569` и `:592`→`:595`**, потому что цель сдвинул этот же коммит; текст ячеек не менялся, длины равны. В `PROGRESS.md` на прежнем месте оставлен заголовок-указатель, поэтому ссылки «секция „Бэклог“» доезжают в один хоп. ## Бэклог (ЕДИНЫЙ, собран 26.07, актуализация 04.08 D39.99/D39.101; правки — только через оркестратора) @@ -288,7 +288,7 @@ | 354 | ⛔ **ОПЛАЧЕННЫЙ `2xx` С ОБОРВАННЫМ ТЕЛОМ — КЛАСС, СТОИВШИЙ 32 % ЖИВОГО ПРОГОНА, И СТРАНИЦА КВИРКОВ О НЁМ МОЛЧИТ.** Живой прогон 08.09 (10 глав, `committed=$1.259217`): два вызова редактора вернулись `2xx` с нечитаемым JSON и были ОПЛАЧЕНЫ — `$0.407606`, то есть **32 % бюджета прогона**. Движок класс ЗНАЕТ и обрабатывает штатно: `backend/internal/pipeline/disposition.go:79`=`FlagDecodeError FlagReason` `FlagDecodeError` — «2xx with an unreadable body — billed, conservatively settled, flagged». ⛔ Но `docs/experiments/00-provider-quirks.md` — страница, которую канон велит читать ПЕРЕД правкой адаптеров и вызовов, — о нём не несёт НИ СТРОКИ: греп по `decode`/`unexpected end of JSON`/«оборван» даёт **0 при 250 строках файла** (контроль: слово `provider` там встречается 3 раза, то есть спрошено существующее). ⇒ следующая сессия, читающая квирки перед платным прогоном, не узнает о поведении, которое съедает треть бюджета. ⛔ **испр. 08.09: МЕХАНИЗМ НАЗВАН ЗДЕСЬ НЕВЕРНО — это не свойство провайдера, а дефект движка, и носитель у него теперь строка 360.** Полигон замером exp-23 п.5 показал, что за подписью «нечитаемое тело» стоит ТАЙМАУТ ЧТЕНИЯ по `attempt_s`, который движок сам же выбрасывает в `_`. ⇒ эта строка остаётся ТОЛЬКО про пробел страницы квирков: она обязана предупреждать, ЧТО оператор увидит и почему, со ссылкой на 360, а не приписывать поведение вендору. ⚠ Зона — ПОЛИГОН (`docs/experiments/` по зонной таблице), живой сессии полигона нет; строка заведена оркестратором как носитель, писать в файл полигону | полигон | скоро (перед следующим платным прогоном любой зоны) | строка в `00-provider-quirks.md` с частотой, стоимостью и тем, что движок делает с классом | живой прогон 08.09, пак «правда оператору и деньги» | | 356 | **КНИГА У СВОЕГО ПОЖИЗНЕННОГО ПОТОЛКА РОЛИ НАВСЕГДА ОСТАЁТСЯ БЕЗ КОНСОЛИДИРОВАННОГО БАНКА, и ни одно сообщение не говорит именно этого.** `store/ledger.go` `RoleSpentUSD` суммирует чекпойнты роли за ВСЮ жизнь книги, предполётный срез сеет `probe := spent` и обрывается на `probe+want > budgetUSD`; при `fits == 0` цикл попыток не запускается вовсе. ⚠ Оператор ПРИ ЭТОМ извещён — `terminologist.go:936` печатает `batches_planned=N batches_running=0 batches_dropped=N spent_usd budget_usd` ДО первого вызова (сверено оркестратором 08.09), поэтому «тихо» здесь неверно, и находка приёмки в этой части ОТКЛОНЕНА доводом зоны. Верен остаток: сообщение говорит про ЭТОТ прогон, а не про то, что книга исчерпала пожизненный бюджет роли и консолидации не получит НИКОГДА, сколько её ни перезапускай. Свойство `budget_usd`, а не фильтра пака «правда оператору и деньги»: тот на решение бюджета не влияет. ⇒ решить, что показывать оператору и владельцу: отдельное сообщение «книга у пожизненного потолка роли», подъём потолка как явный акт, или сознательное молчание с записью | бэкенд | когда-нибудь (гейт: первая книга, упёршаяся в пожизненный потолок роли) | сообщение об исчерпании пожизненного бюджета либо ратифицированное молчание | приёмка `D39.226`, линза «вне карты» | | 359 | **ТРИ ЯКОРЯ В ЧУЖИХ ЗОНАХ ПРОТУХЛИ ОТ ДВУХ ДВИЖКОВЫХ ЛЕНДИНГОВ — адреса уже найдены, править владельцам зон.** Линтер `counts.py --lint` даёт 6 проблемных якорей при 121 живом доке; три из них — фронтовые и пингнуты в `frontend/docs/frontend-PROGRESS.md` (зона ЗАМОРОЖЕНА). Остальные три, с новыми адресами, снятыми оркестратором 08.09: **(а) полигон** — `docs/experiments/23-editor-tier.md:7739` и `:7740` ссылаются на «×2 дешевле glm» в `configs/pipeline-c1.yaml:95` и `pipeline-arm-glm.yaml:6`; ⚠ цель ИСЧЕЗЛА, а не сдвинулась (греп даёт 0): долларовые числа сняты из шапки арма приёмкой `D39.226`, потому что стояли на опровергнутом ценовом базисе — значит якорь надо не пере-навести, а пере-написать по факту. **(б) бэкенд** — `backend/docs/DISCLOSURE_LAW_DESIGN.md:40` ссылается на `internal/pipeline/quality.go:233`, токен уехал на `:235`. ⚠ Оркестратор в чужие зоны не пишет; строка — носитель, чтобы адреса не искали заново | полигон + бэкенд | когда-нибудь (попутно с любым касанием этих файлов) | пере-наведённый якорь у бэкенда, пере-написанный по факту — у полигона | `counts.py --lint` 08.09 | -| 360 | ⛔⛔ **ТАЙМАУТ ЧТЕНИЯ МАСКИРУЕТСЯ ПОД НЕЧИТАЕМОЕ ТЕЛО, РЕТРАИТСЯ И БИЛЛИТСЯ ПО ОЦЕНКЕ — 23–34 % ЦЕНЫ РЕДАКТОРСКОГО ПРОГОНА, И НОСИТЕЛЯ У КЛАССА НЕ БЫЛО.** Механизм доказан кодом: `backend/internal/llm/httpllm.go` в чтении ответа делает `data, _ := io.ReadAll(io.LimitReader(...))` — **ошибка чтения выбрасывается**, а обрыв распознаётся по РАЗМЕРУ (читают на байт дальше лимита), поэтому тело, оборванное дедлайном `attempt_s` посреди генерации, от целого НЕ ОТЛИЧИМО. Обрезок идёт в `json.Unmarshal` → `unexpected end of JSON input` → `BilledDecodeError` («2xx body decode failed (call IS billed)»), и возвращается он с флагом retryable **`true`** ⇒ вызов повторяется. ⚠ **испр. 08.09, две мои ошибки, обе пере-проверены по коду:** (1) ретрай ОДИН, а не `max_attempts` — `httpllm.go` «The first occurrence retries once … a second billed-decode is terminal»; (2) сеттл ОДИН на стадию-вызов, а не на попытку — `stagerun.go` зовёт `SettleWithCheckpoint(resv, estimate, …)` единожды, `att.cumCost, att.runCost = estimate, estimate`. ⇒ **направление ошибки ОБРАТНОЕ тому, что я записала:** два HTTP-вызова к провайдеру дают ОДНУ запись в леджер, то есть леджер НЕДО-считает на один полный счёт, если вендор биллит оба. Пользователь не переплачивает — недо-счёт бьёт по деплою, ровно как и говорит ратифицированная строка **331**. ⛔⛔ **И РАЗЛИЧИТЕЛЬ ВЫБРАН НЕ ПО ТОМУ ПРИЗНАКУ — вот точная форма дефекта.** Код о двойной оплате ЗНАЕТ и от неё защищается, дословно: «A truncated (>16 MiB) body is deterministic — retrying is pointless (**each retry is a new billed 2xx**), so terminal; a broken connection / garbage is retryable». Но защита ключуется РАЗМЕРОМ, а тело, оборванное НАШИМ дедлайном, маленькое ⇒ проваливается в ветвь «broken connection» и ретраится, хотя довод «каждый ретрай — новый оплаченный 2xx» относится к нему в полной мере. ⇒ лечение не в новом механизме, а в ПРИЗНАКЕ: отличать «мы сами оборвали чтение» от «провайдер прислал мусор». ⭐ **И это ровно то, что `research/21` §Q6 запретил заранее:** в списке «не берём» стоит «reclass empty-2xx как retryable (**double-bill**)». ⛔ Замер полигона (`docs/experiments/23-editor-tier.md`, п.5 «ЧЕТЫРЕ ТАЙМАУТА»): латентности `480390 / 480505 / 480577 / 480528` мс = **ровно 2 × `attempt_s: 240`**, класс назван `decode_error`, цена — **$0.475893 = 23.8 % руки строками, $0.689215 = 34.5 % юнитами**; полигон квалифицировал это как дефект ДВИЖКА и пингнул оркестратору («не наша зона»). ⚠ **Носителя не было: греп `attempt_s` и «таймаут чтения» по живому бэклогу — 0 хитов при 255 рядах** (контроль: `decode` — 3 хита), то есть спрошено существующее. ⛔ **И это ТА ЖЕ физика, что строка 331** — «вызов оборвали МЫ, провайдер, возможно, уже сгенерировал и выставит счёт», — но движок закрывает её ПРОТИВОПОЛОЖНО: оценкой здесь и НУЛЁМ в ветви `No 2xx ever arrived: nothing was billed` (`stagerun.go`). ⚠ **испр. 08.09: прежняя редакция называла спусковым крючком нулевой ветви ПОТОЛОК — это протухло.** Замер `PD-441` снят 04.09, а `81a89e9` (05.09, `D39.206`) сменил поведение: потолок теперь останавливает ДОПУСК, а летящие вызовы доигрывают и биллятся нормально (`waverun.go`: «A ceiling no longer cancels, so siblings run on» и «stopped admissions … every skipped item is simply not started»). ⇒ конкретная дорогая картина из `PD-441` (три вызова по 120–156 с, отменённые в один миг и ушедшие в леджер нулём) СЕГОДНЯ НЕ ВОЗНИКАЕТ. Нулевая ветвь ЖИВА и достижима иначе — стоп прогона, убитый процесс, дедлайн до первого байта, — и расхождение бухгалтерии остаётся, но дешевле и реже, чем я записала. ⚠ **Граница честности:** что два вызова прогона 08.09 ($0.407606 = 32 % бюджета, «unexpected end of JSON input») были именно таймаутами — ВЫВЕДЕНО по совпадению подписи с замером полигона, а НЕ измерено: сырого леджера того прогона в дереве нет (`.db` новее 07.09 — 0 при 163). ⇒ инженерная половина (не выбрасывать ошибку чтения, отличать таймаут от нечитаемого тела, не ретраить вызов, оборванный СВОИМ дедлайном) — заказ без развилок; ДЕНЕЖНАЯ половина требует слова владельца: платит ли пользователь за вызов, который оборвали мы, и если да — по оценке или по нулю. Пока ответа нет, платформа биллит `committed_usd`, куда оценка уже попала | бэкенд | блокер-очереди для ПЛАТНЫХ прогонов (класс съедает четверть-треть бюджета; $0-работа им не блокируется) | ошибка чтения не выбрасывается · таймаут отличён от нечитаемого тела и НЕ ретраится · денежная диспозиция ратифицирована владельцем · строка 331 закрыта тем же решением | замер полигона exp-23 п.5 + разбор Fable 5 08.09, пере-проверено оркестратором по коду | +| 360 | ⛔⛔ **ТАЙМАУТ ЧТЕНИЯ МАСКИРУЕТСЯ ПОД НЕЧИТАЕМОЕ ТЕЛО, РЕТРАИТСЯ И БИЛЛИТСЯ ПО ОЦЕНКЕ — 23–34 % ЦЕНЫ РЕДАКТОРСКОГО ПРОГОНА, И НОСИТЕЛЯ У КЛАССА НЕ БЫЛО.** Механизм доказан кодом: `backend/internal/llm/httpllm.go` в чтении ответа делает `data, _ := io.ReadAll(io.LimitReader(...))` — **ошибка чтения выбрасывается**, а обрыв распознаётся по РАЗМЕРУ (читают на байт дальше лимита), поэтому тело, оборванное дедлайном `attempt_s` посреди генерации, от целого НЕ ОТЛИЧИМО. Обрезок идёт в `json.Unmarshal` → `unexpected end of JSON input` → `BilledDecodeError` («2xx body decode failed (call IS billed)»), и возвращается он с флагом retryable **`true`** ⇒ вызов повторяется. ⚠ **испр. 08.09, две мои ошибки, обе пере-проверены по коду:** (1) ретрай ОДИН, а не `max_attempts` — `httpllm.go` «The first occurrence retries once … a second billed-decode is terminal»; (2) сеттл ОДИН на стадию-вызов, а не на попытку — `stagerun.go` зовёт `SettleWithCheckpoint(resv, estimate, …)` единожды, `att.cumCost, att.runCost = estimate, estimate`. ⇒ **направление ошибки ОБРАТНОЕ тому, что я записала:** два HTTP-вызова к провайдеру дают ОДНУ запись в леджер, то есть леджер НЕДО-считает на один полный счёт, если вендор биллит оба. Пользователь не переплачивает — недо-счёт бьёт по деплою, ровно как и говорит ратифицированная строка **331**. ⛔⛔ **И РАЗЛИЧИТЕЛЬ ВЫБРАН НЕ ПО ТОМУ ПРИЗНАКУ — вот точная форма дефекта.** Код о двойной оплате ЗНАЕТ и от неё защищается, дословно: «A truncated (>16 MiB) body is deterministic — retrying is pointless (**each retry is a new billed 2xx**), so terminal; a broken connection / garbage is retryable». Но защита ключуется РАЗМЕРОМ, а тело, оборванное НАШИМ дедлайном, маленькое ⇒ проваливается в ветвь «broken connection» и ретраится, хотя довод «каждый ретрай — новый оплаченный 2xx» относится к нему в полной мере. ⇒ лечение не в новом механизме, а в ПРИЗНАКЕ: отличать «мы сами оборвали чтение» от «провайдер прислал мусор». ⭐ **И это ровно то, что `research/21` §Q6 запретил заранее:** в списке «не берём» стоит «reclass empty-2xx как retryable (**double-bill**)». ⛔ Замер полигона (`docs/experiments/23-editor-tier.md`, п.5 «ЧЕТЫРЕ ТАЙМАУТА»): латентности `480390 / 480505 / 480577 / 480528` мс = **ровно 2 × `attempt_s: 240`**, класс назван `decode_error`, цена — **$0.475893 = 23.8 % руки строками, $0.689215 = 34.5 % юнитами**; полигон квалифицировал это как дефект ДВИЖКА и пингнул оркестратору («не наша зона»). ⚠ **Носителя не было: греп `attempt_s` и «таймаут чтения» по живому бэклогу — 0 хитов при 255 рядах** (контроль: `decode` — 3 хита), то есть спрошено существующее. ⛔ **И это ТА ЖЕ физика, что строка 331** — «вызов оборвали МЫ, провайдер, возможно, уже сгенерировал и выставит счёт», — но движок закрывает её ПРОТИВОПОЛОЖНО: оценкой здесь и НУЛЁМ в ветви `No 2xx ever arrived: nothing was billed` (`stagerun.go`). ⚠ **испр. 08.09: прежняя редакция называла спусковым крючком нулевой ветви ПОТОЛОК — это протухло.** Замер `PD-441` снят 04.09, а `81a89e9` (05.09, `D39.206`) сменил поведение: потолок теперь останавливает ДОПУСК, а летящие вызовы доигрывают и биллятся нормально (`waverun.go`: «A ceiling no longer cancels, so siblings run on» и «stopped admissions … every skipped item is simply not started»). ⇒ конкретная дорогая картина из `PD-441` (три вызова по 120–156 с, отменённые в один миг и ушедшие в леджер нулём) СЕГОДНЯ НЕ ВОЗНИКАЕТ. Нулевая ветвь ЖИВА и достижима иначе — стоп прогона, убитый процесс, дедлайн до первого байта, — и расхождение бухгалтерии остаётся, но дешевле и реже, чем я записала. ⚠ **Граница честности:** что два вызова прогона 08.09 ($0.407606 = 32 % бюджета, «unexpected end of JSON input») были именно таймаутами — ВЫВЕДЕНО по совпадению подписи с замером полигона, а НЕ измерено: сырого леджера того прогона в дереве нет (`.db` новее 07.09 — 0 при 163). ⇒ инженерная половина (не выбрасывать ошибку чтения, отличать таймаут от нечитаемого тела, не ретраить вызов, оборванный СВОИМ дедлайном) — заказ без развилок; ДЕНЕЖНАЯ половина требует слова владельца: платит ли пользователь за вызов, который оборвали мы, и если да — по оценке или по нулю. Пока ответа нет, платформа биллит `committed_usd`, куда оценка уже попала ⭐ **КОНСТРУКЦИЯ ЛЕЧЕНИЯ СОШЛАСЬ (консилиум с чистым Fable 5, 08.09; обе стороны объявили сходимость).** **Граница денег — НЕ статус-строка, а факт ДОСТАВКИ запроса**, и он измеряется `httptrace.ClientTrace` (`WroteRequest` / `GotFirstResponseByte`) без байта на проводе — различение проверено исполнением на клиенте той же сборки. Отсюда: **(1)** ошибка `ReadAll` не выбрасывается; после 2xx пять исходов, до заголовков три; **(2)** self-cut (наш дедлайн при доставленном запросе) — свой класс `attempt_timeout`, **чекпойнт + флаг, НЕ ретраить**, и это же чинит ТРЕТЬЮ утечку: сегодня нулевая ветвь `releaseReservation` чекпойнта не пишет ⇒ **резюм ПЕРЕКУПАЕТ вызов** (проверено, в строке 331 этого нет); **(3)** запрос НЕ доставлен (дедлайн до `WroteRequest`) — единственный случай, где «ничего не куплено» верно по построению: как сегодня; **(4)** разрыв соединения после доставки — один ретрай, на исчерпании инфра-пауза, не флаг; **(5)** дедлайн = `clamp(queue_slack_s + max_tokens/tok_s_floor, attempt_s, attempt_max_s)`, пол по умолчанию 35.5 tok/s (единственный опубликованный вендором), `attempt_s` становится ПОЛОМ ⇒ ни один конфиг не теряет времени; в снапшот не входит (`AttemptS` — 0 из 120 файлов); **(6)** сумма сеттла — за ОДНОЙ функцией `settleUSDForCutCall`, не YAML-ручкой: денежную политику нельзя переключать молча конфигом. ⚠ **Тест-тождество `8496 → 239 с` обязан пинить ФОРМУЛУ, а не число** — иначе через полгода его «починят» под красивую константу. ⛔ Расходится ровно ОДИН бит и он владельца: что уходит в сеттл — оценка или ноль; всё остальное одинаково во всех формах, поэтому пак пишется ДО его слова | бэкенд | блокер-очереди для ПЛАТНЫХ прогонов (класс съедает четверть-треть бюджета; $0-работа им не блокируется) | ошибка чтения не выбрасывается · таймаут отличён от нечитаемого тела и НЕ ретраится · денежная диспозиция ратифицирована владельцем · строка 331 закрыта тем же решением | замер полигона exp-23 п.5 + разбор Fable 5 08.09, пере-проверено оркестратором по коду | | 361 | **ДВА ГЕЙТА ОДНОГО ФАЙЛА ТЯНУТ ЯЧЕЙКУ В РАЗНЫЕ СТОРОНЫ: экранирование `\|` по счёту колонок ЛОМАЕТ сверку токена якоря.** Счёт ячеек требует экранировать черту в тексте ячейки; сверка якоря сравнивает токен с кодом ДОСЛОВНО, а в коде черта без слеша. ⇒ токен, внутри которого есть `|`, после законного экранирования перестаёт совпадать с целью, и линтер кричит «цель уехала» на невиновного. Замерено платформенной сессией 08.09 на `PD-422`: якорь `internal/runs/runs.go:408` держал токен `resnapshot := book.BankMoved || book.HasPriorRun`; после экранирования черт по §4.5 токен перестал совпадать, и сессия починила это УКОРОЧЕНИЕМ токена — то есть заплатила точностью якоря за форму таблицы. ⇒ следующий, кто пойдёт экранировать черты, наступит на то же и так же потеряет точность. **Направление:** `norm()` в `docs/scripts/counts.py` снимает экранирование `\|` → `|` перед сверкой — шаг детерминированный и симметричный, как и схлопывание пробелов, которое та же функция уже делает по тому же доводу; докстринг «пробельная нормализация — и только она» правится вместе с поведением, иначе останется вторым носителем. ⚠ Укороченный токен `PD-422` после починки можно вернуть к полному | оркестратор | скоро | `norm()` снимает экранирование, селф-тест на случай `a \|\| b`, докстринг переписан | находка платформенной сессии 08.09 при исполнении пака разреза приёма | | 362 | **РЕМОНТ ПРОТИВ РЕМОНТА: несколько ремонтов одного чанка покупают ДОКАЗУЕМО НЕПЕРЕСЕКАЮЩИЕСЯ спаны и вытесняют друг друга в `superseded`.** Третий член семьи строк 355/358: адрес `{глава, чанк, стадия}` предполагает, что поздний вызов ЗАМЕЩАЕТ ранний, а ремонт переписывает ОГРАНИЧЕННЫЙ спан, не весь выход стадии. ⚠ Механизм здесь НЕ пересказан — он расписан в самом коде, `backend/internal/pipeline/paidtail.go` (абзац «The repair role shares that address WITHOUT sharing that property»), и там же названо, почему очевидное лечение не работает: разведение по РОЛИ ремонты между собой не различает — «every one of them carries the same role and only the span tells them apart». ⛔ Нынешнее поведение ЗАПИНЕНО КАК НЕПОДВИНУТОЕ, а не как верное — `TestTheRoleSplitDoesNotMoveARepairsMoney`; то есть пин охраняет отсутствие тихого сдвига, а не правильность. **Цена, замер ревьюера движковой сессии 08.09: 77 % денег чанка отчитаны как «купившие ничего»** ⚠ (оркестратором НЕ пере-мерено — взято со слов замера, механизм проверен по коду). Не взято в пак «движок уже знает» сознательно: вторая денежная поверхность в одном лендинге | бэкенд | скоро | ремонты одного чанка различены по спану, а не по триплету; пин утверждает ПРАВИЛЬНОСТЬ, а не неподвинутость | приёмка пака «движок уже знает» 08.09 | | 363 | **ПОЛНЫЙ ТЕКСТОВЫЙ САЙДКАР МОЛЧИТ О НЕПОЛНОТЕ БАНКА — а баннер зовёт в него ровно тогда, когда строк больше капа.** После пака «движок уже знает» факт неполноты несут машинный сайдкар и stdout-экран; текстовый сайдкар стоп-таблицы выведен из заказа §4.1 сознательно и остался единственной поверхностью, которая о неполноте не говорит. ⚠ Цена именно в связке: stdout капирован 20 строками, и когда строк больше, баннер отправляет читателя в ПОЛНЫЙ текстовый сайдкар — то есть туда, где факта нет. ⇒ на большой книге оператор, послушавшийся баннера, теряет ровно то, что пак довозил | бэкенд | скоро | текстовый сайдкар несёт полноту наравне с двумя другими поверхностями | пинг движковой сессии, приёмка пака «движок уже знает» 08.09 | @@ -296,3 +296,5 @@ | 365 | ⛔ **ПОТОЛОК `TM_PLATFORM_UPLOAD_DEADLINE` ТИХО ОПУСТИЛСЯ, И ДЕПЛОЙ, КОТОРЫЙ ВЧЕРА СТАРТОВАЛ, СЕГОДНЯ ОТКАЖЕТ НА БУТЕ.** Пак разреза приёма поднял `UploadSettle` 210 → 220 с и добавил `ClaimGrace` третьим окном в бутовую сверку (`internal/config/config.go`, `min(sweepGrace, parseClaimGrace, idempotencyClaim)`), из-за чего самое узкое окно стало 20 мин вместо 30 ⇒ допустимый дедлайн загрузки упал с ~26 м 30 с до ~16 м 20 с. ⚠ `deploy/README.md` правило называет, но НЕ называет ни числа, ни того, что граница ПЕРЕЕХАЛА: оператор с легальным вчера `TM_PLATFORM_UPLOAD_DEADLINE=20m` получит отказ старта без апгрейд-ноты. ⇒ либо нота в рантбуке с числом и причиной, либо сообщение бута, называющее, ЧТО именно сузилось | платформа | скоро (до следующего выката) | оператор узнаёт о сужении из рантбука или из текста отказа, а не из отказа старта | охотник вне карты, приёмка пака разреза приёма 08.09 | | 366 | **ОЖИДАНИЕ СЛОТА ЗАНИМАЕТ ВОРКЕР ОЧЕРЕДИ, А ТЕЛЕМЕТРИЯ ПОТОЛКА ПУБЛИКУЕТСЯ ЗА ПАССОМ, КОТОРЫЙ НА ЭТОТ ПОТОЛОК И БЛОКИРУЕТСЯ.** Две половины одного: **(а)** все три вида заданий (`SpawnArgs`, `ParseArgs`, `ExportArgs`) делят ОДИН пул `river.QueueDefault` размером `DefaultWorkers`, а `DefaultMaxCuts = jobs.DefaultWorkers` — числа совпадают ПО ПОСТРОЕНИЮ ⇒ четыре HTTP-загрузки, занявшие слоты, ставят четыре `tm_parse_book` в блокировку внутри `Work()`, и очередь целиком — спавн платных прогонов, сборка экспортов — стоит за ними до `deadline − reserve` = **13,5 мин**. Механизм `giveBack`/`RetryDelay 30s`, который пак и построил, при этом почти недостижим: джоба доходит до снуза, просидев эти минуты. **(б)** `CutCapacity()` → `ObserveCuts` вызывается в `one()` ПОСЛЕ пасса интейка, а тот сам может ждать слот до 13,5 мин ⇒ гейджи `cuts_in_flight`/`cuts_waiting` замирают ровно в тот момент, ради которого заведены. Комментарий `observe` закрывает случай мёртвой БД и не видит случая насыщенного потолка. ⚠ Ни граница ожидания, ни занятость общего пула не названы ни в отчёте зоны, ни в акте `D39.229` — там названа ДРУГАЯ граница (строка 364) | платформа | скоро | ожидание не занимает воркер очереди ЛИБО граница названа числом в рантбуке; телеметрия потолка публикуется независимо от блокирующего пасса | охотник вне карты, приёмка пака разреза приёма 08.09 | | 367 | **ГАРАНТИЯ РЕЗЕРВА У ПОТОЛКА РАЗРЕЗОВ СУЩЕСТВУЕТ ТОЛЬКО ПОД КОНКУРЕНЦИЕЙ, А ТРИ КОММЕНТАРИЯ УТВЕРЖДАЮТ ОБРАТНОЕ.** `platform/internal/books/limit.go`, `takeCutSlot`: удачный `sem.TryAcquire(1)` возвращает слот СРАЗУ, не спросив `worthStarting` — то есть на свободном хосте слот выдаётся при любом остатке бюджета, хоть при миллисекундах. Проверка стоит только на пути ожидания. ⛔ Три места говорят иначе: «`reserve` is what a WON slot must still be worth» (`limit.go`), «a cut is not started … unless what remains is what this platform calls a cut's worth of time» (`parse.go`), «Below zero there is no cut to run, and it is not ATTEMPTED» (`books.go`). ⚠ Сегодня не кусает — у интейка `reserve = 0`, у очереди `JobTimeout` 15 мин, свип гарантирует запас, — но защита отсутствует ровно на пути, который проходится почти всегда, и **любой пин на неё зелёный навсегда**: он попадает только в ветку ожидания. Это тот самый класс вырожденного пина, против которого построена вся смена. ⇒ лечение скорее всего однострочное (спросить `worthStarting` ДО `TryAcquire`), но форма и цена — зоне. ⚠ Не взято дофиксом сознательно: правило остановки по этому паку объявлено ДО находки, и нарушать его задним числом значит обесценить сам механизм остановки | платформа | скоро | гарантия резерва действует на ОБОИХ путях ЛИБО три комментария приведены к правде; пин достигает быстрого пути | охотник вне карты, приёмка пака разреза приёма 08.09 | +| 368 | **ОПЕРАТОРСКИЙ ЭКРАН ЗАНИЖАЕТ ОЦЕНОЧНЫЕ ТОКЕНЫ В 5 РАЗ.** `est_tokens` в `request_log` считается `r.estOutTokens(ch.Text)` по фертильности видимого перевода, а у DeepSeek размышление сидит ВНУТРИ `completion_tokens` ⇒ на строках `decode_error` стоит 2887/2854/3162, тогда как соседние попытки тех же чанков реально дали **16 000** (`finish=length`). Занижение **5.0–5.6×**. Читатель — `cmd/tmctl/render.go` («estimated-cost rows: N … ~M est. output tok»): оператор видит впятеро меньше правды. ⚠ В `cost_usd` это число НЕ входит — деньги считаются грантом, — то есть врёт легенда, а не счёт; но врёт она ровно тому, кто по ней решает, продолжать ли платить. Класс «прибор отвечает на свой вопрос» (`D39.202`). Находка консилиума с чистым Fable 5, 08.09 | бэкенд | скоро | легенда считает то же, что считает счёт, либо честно говорит, чего не знает | консилиум по строке 360, 08.09 | +| 369 | ⛔ **ОСЬ ЭСКАЛАЦИИ D2 СЕГОДНЯ ФИКТИВНА: БРОНИРУЕТСЯ 32 000 ТОКЕНОВ, ДОСТИЖИМО ~19 400.** Реген удваивает `max_tokens` 16 000 → 32 000 и НЕ трогает время, а `attempt_s: 240` при 70–90 tok/s даёт потолок **17–21 тыс. токенов**. Замер по 20 регенам трёх DeepSeek-армов (`docs/experiments/23-editor-tier.md` + леджеры `probe-4axes/arms/`): доставлено первой попыткой 17, обрезано 3; максимум, когда-либо доставленный регеном — **19 354 = 60 % гранта**; **13 доставок из 17 уложились бы в ИСХОДНЫЙ грант 16 000** ⇒ реген «срабатывает» пере-броском длины размышления, а не бо́льшим бюджетом. ⚠ Цена не только в обрывах: резервация бронирует полные 32 000 ($0.127 выхода) при досягаемых ~$0.08 — фикция съедает headroom под книжным потолком. ⇒ дедлайн от бюджета (строка **360**) — не подкрутка таймаута, а то, что делает эту ось настоящей; закрывать вместе с ней. Категорика, которая держится: реген не может использовать верхние ~40 % выданного, и всякий, кому эскалация РЕАЛЬНО нужна, обрезается по построению | бэкенд | скоро | эскалация досягаема по времени либо не бронирует недостижимое | консилиум по строке 360, 08.09 | diff --git a/docs/research/21-llm-transport-survey.md b/docs/research/21-llm-transport-survey.md index d884c9a7..dcae9c8f 100644 --- a/docs/research/21-llm-transport-survey.md +++ b/docs/research/21-llm-transport-survey.md @@ -6,6 +6,11 @@ > **→ ПОСТРОЕНО паком-12 «транспорт-гигиена» (24.07, `7fe0a5b`; отчёт `docs/archive/reports/TRANSPORT_PACK12_REPORT_2026-07-24.md`; отметка 25.07, оркестратор №7):** §1-пункты 1–6/8a/8b/9 реализованы (quota-429→terminal · дробный Retry-After · HTTP/2 keepalive · джиттер · xAI identity-гард · GLM finish-нормализация · пин retryable-мапы · кап billed-decode · models.yaml-валидация); пункты 7 (CostSource) и 10 (луп-гард) — паком-13 (`2f91b04`). DEFER снят; Q7-абсорбция в `00-provider-quirks.md` — по-прежнему только после вендор-сверки. +> ⛔⛔ **ЭРРАТА ОРКЕСТРАТОРА 08.09 — §Q6 НЕВЕРЕН ДВАЖДЫ, И ОБЕ ПОЛОВИНЫ ВМЕСТЕ ДЕРЖАЛИ НЕВЕРНЫЙ ДЕДЛАЙН ПОЛТОРА МЕСЯЦА.** Найдено консилиумом с чистым Fable 5 (08.09), пере-проверено оркестратором. +> **(а) Конверт 240 с выведен неверно.** §Q6 сертифицирует `attempt_s=240` как «внутри сертифицированного вендором безопасного non-streaming конверта» по математике Anthropic. Формула SDK — 128 000 токенов в час = **35.5 tok/s**, то есть 240 с покрывают **~8.5k токенов**, а редактор бюджетирован на 16 000 с эскалацией до 32 000. `anthropic-sdk-go` при `expectedTime > 10 min` (max_tokens > 21333) ОТКАЗЫВАЕТ: «streaming is required». Наш реген на 32 000 этот SDK не выпустил бы вовсе. ⭐ Та же формула, применённая к ЧЕРНОВИКУ (8 496 токенов), даёт **238.95 с** — то есть 240 верно ровно для черновика и было перенесено на редактора слепо. +> **(б) «Сервер держит headers до конца генерации» — неверно для нашего боевого провайдера, и это половина, которая всё спрятала.** DeepSeek документирует (`api-docs.deepseek.com/quick_start/rate_limit`) ранний `200` с пустыми строками, пока запрос ждёт планирования, и закрытие соединения, если инференс не начался за 10 минут. В наших же логах это видно буквально: `status=200 … response="\n"`. ⇒ на DeepSeek ветвь «No 2xx ever arrived» НЕДОСТИЖИМА по построению, и ВСЕ наши self-cut прятались под `decode_error`. Контроль: строка «attempt timed out after» — **0 хитов при 547 лог-файлах**, при 28 строках «2xx body decode failed» в тех же файлах. +> ⇒ Вердикт §Q6 «держим non-streaming дефолтом» ОСТАЁТСЯ в силе — стриминг не спас бы ни одно из 19 замеренных событий, — но его trip-wire («тихое усечение») сформулирован не по тому признаку: усечения на диске ГРОМКИЕ (`finish=length` → реген). Переписан в строке бэклога **360**: доля self-cut в спенде редактора ПОСЛЕ починки дедлайна. Носитель лечения — строки **360** и **369**. + ## Метод, лицензии, надёжность - **Оркестрация.** 11 репо-агентов (survey Q1–Q6) ∥ 6 провайдер-агентов (quirk-mining Q7-код) → 6 синтез-агентов (по вопросу) + Q7-дедуп → адверсариальная верификация (author≠reviewer): 57 агентов, 0 ошибок. Онлайн-жатва трекеров (Q7) — отдельно, из главной сессии (`gh` + WebFetch).