diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index fac5417a..e6d656ad 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -407,7 +407,7 @@ | 496 | ⛔ **ЦЕЛЬ `battery-stand` НА GIT-ВОРКРИ НЕ ПРОХОДИТ ПО ПОСТРОЕНИЮ, И ЕЁ СОБСТВЕННОЕ ОБЕЩАНИЕ ИСПОЛНЯЕТСЯ НАОБОРОТ.** Рецепт ставит флаги ВНУТРИ команды (`backend/Makefile:140`=`TM_MINER_PARITY=1 TM_CHECKER_LABELS=1`), то есть у оператора выбора нет — «я неудачно запустил» тут неверный диагноз. `standdata.Root()` ищет корень по маркеру (`backend/internal/standdata/standdata.go:35`=`Root finds the repository root by MARKER`) и в воркри разрешается в корень ВОРКРИ, где `eval/.gitignore` исключает `data/` правилом-классом ⇒ путь по умолчанию указывает туда, где файла не может быть никогда. Замер 17.09: `eval/exp16/data` в воркри отсутствует, гиту файл неизвестен (`git ls-files --error-unmatch` — 0), в основном чекауте лежит (5 071 852 байта), и цель падает `TM_MINER_PARITY=1 but required data is missing`. ⛔ Комментарий цели обещает «при отсутствии данных ПАДАТЬ, чтобы наличие корпуса УТВЕРЖДАЛОСЬ, а не предполагалось» — на воркри она утверждает отсутствие стенда, которого нет только здесь, и её сообщение читается как находка о дереве. ⚠ Лечение — НЕ оверрайд пути: `TM_MINER_PARITY_CONTRAST=<путь в основном чекауте>` живёт на одной машине и умрёт при переносе, как умирают вычисленные адреса. У артефакта есть воспроизводимое происхождение — регенерируется из jieba 0.42.1 с запиненным SHA (`docs/experiments/16-bank-mining.md:183`=`jieba_dict_general_zh.txt`) ⇒ цель обязана либо регенерировать его, либо объявлять отсутствие стенда ОТДЕЛЬНЫМ исходом, а не падением. Пять корпусных тестов при поданном стенде проходят — прибор годен, негоден его путь по умолчанию | бэкенд | скоро | цель проходит на воркри без ручных оверрайдов, либо печатает «стенда нет» отдельным исходом, отличимым от падения | замер оркестратора 17.09 при приёмке `D39.266` | | 498 | ⛔⛔ **`prompt_override` НЕ СПРАШИВАЕТ ПРО ПАРУ, И ВСЕ ЧЕТЫРЕ БОЕВЫХ КОНФИГА УЖЕ НАЦЕЛЕНЫ НА `zh-ru` ⇒ АНГЛИЙСКАЯ КНИГА ПОЕДЕТ КИТАЙСКИМ ПРОМТОМ ПЕРЕВОДЧИКА МОЛЧА.** Конвенционный путь `//.md` падает ГРОМКО на отсутствующей паре именно затем, чтобы конвенции чужой пары не подставлялись тихо; `prompt_override` проверяет только ЧИТАЕМОСТЬ файла и про пару не спрашивает никогда. Замер с контролем (`tmctl manifest` на настоящей английской книге, `source_lang: en`): override на настоящий `backend/prompts/zh-ru/translator-banknote.md` → **`EXIT=0`**, конфиг грузится и в той же сводке печатает `en→ru`; контроль — override на несуществующий файл того же каталога → `EXIT=10` «prompt_override template … is not readable». ⇒ гард есть, но он про файл, а не про принадлежность. Зашито во ВСЕХ боевых: `backend/configs/pipeline-c1.yaml:77`, `backend/configs/pipeline-arm-glm.yaml:38`, `backend/configs/pipeline-arm-mistral.yaml:42` (все `../prompts/zh-ru/translator-banknote.md`), `backend/configs/pipeline-c2.yaml:43` (`../prompts/zh-ru/judge-selector.md`). ⛔ **Цена конкретна и она в КАЧЕСТВЕ, а не в падении:** английская книга получит промт, который велит «китайский 时辰 = 2 часа», «доля 成 — десятые» и требует банкнотную таблицу по китайским правилам. ⚠ Класс — «промт проверен НАЛИЧИЕМ файла, а не тем, чему он принадлежит»; это дверь, через которую возвращается латентный баг, ради которого конвенционный путь и сделан громким. **Обход данными существует** (своя пайплайн-копия книги с override на свою пару) и применён паком английской пары — но обход прячет цену: следующая пара наступит на то же и наступит молча. **Лечение:** override обязан лежать внутри каталога пары ЭТОЙ книги, иначе громкий отказ, называющий и путь, и пару; осознанный вариант роли (`editor-mono`, банкнота) этим не ломается — он и так в каталоге своей пары. ⚠ `PromptSHA256` берётся по СОДЕРЖИМОМУ, а не по пути ⇒ пере-снапшота правка не требует (предъявить замером до правки конфигов) | бэкенд | **скоро (БЛОКЕР ратификации готовности)** | ⚠ **ИСПР. 18.09 — ПРЕЖНИЙ КРИТЕРИЙ ОПИСЫВАЛ ФОРМУ, КОТОРУЮ МЫ ОТВЕРГЛИ, И ЗАКРЫВАТЬ РЯД ПО НЕМУ НЕЛЬЗЯ.** Он предписывал «гард отказывает на override вне каталога пары книги» — а это отменило бы **ратифицированное** свойство с живым пином `TestPromptOverrideIsPairAgnostic` (`backend/internal/config/prompt_pack_test.go:119`, гоняет три пары), чей докблок объявляет черновую стадию ЕДИНСТВЕННЫМ осознанным исключением из ролевой конвенции и говорит про ключ дословно «which is exactly what the convention keeps that key for». Сессия остановилась и пингнула вместо исполнения — по условию оркестратора, и условие окупилось: разрешение гарда было ошибкой оркестратора, не проверившего ратификацию. **НОВЫЙ КРИТЕРИЙ, по построенному:** ключ `prompt_variant` резолвит вариант роли ПО КОНВЕНЦИИ (`/<пара>/<роль>-<вариант>.md`) — достройка того, что данные уже используют именами `translator-banknote.md`/`editor-mono.md`; `prompt_override` сохраняет ратифицированное пар-агностичное значение и его пин **НЕ тронут ни строкой**; все четыре боевых конфига сняты с литерального `../prompts/zh-ru/…` и переведены на вариант; пин утверждает, что КАЖДАЯ стадия КАЖДОГО боевого конфига резолвится в каталог СВОЕЙ пары, считает по (конфиг, пара) и сообщает недобор числом; записи каталога с флагом `battery` | замер `textmachine-main-c5` 17.09 на английской книге, разрешение оркестратора | | 497 | ⛔ **ПИН КВАДРАТИЧНОСТИ ФЛЕЙКУЕТ НА ЗДОРОВОМ ДЕРЕВЕ И КРАСИТ ЧУЖОЙ ЛЕНДИНГ.** `TestEvidenceWindowsDoesNotGrowQUADRATICALLYWithTheBook` (`backend/internal/terminology/evidence_test.go:77`=`linear ≈4, quadratic ≈16`; размеры входа объявлены строками выше — `measure(2000)` и `measure(8000)`) утверждает ОТНОШЕНИЕ времён, и его докблок объясняет выбор так: «IT ASSERTS A RATIO, NOT A DURATION. A wall-clock threshold on a shared machine is a flake; the shape of the curve is the property». ⛔ **Посылка опровергнута замером: отношение защищает от РОВНОЙ нагрузки и слепо к её ИЗМЕНЕНИЮ между половинами.** Обе половины меряются ПОСЛЕДОВАТЕЛЬНО, а `go test ./...` гоняет пакеты параллельно — выросла контенция между малым и большим замером, и отношение раздулось, ничего об этом не сказав. ⚠ **Улика — два вердикта на ОДНОМ коммите `871e0af`:** приёмка оркестратора дала `MAKE_EXIT=0`, входная батарея следующей сессии — `MAKE_EXIT=2` с `ratio ×16.5` при пороге 16 (малый замер прошёл на 42.5 мс против изолированных 22, то есть уже вдвое медленнее). Изолированно пять прогонов оркестратора дают 4.3 · 4.3 · 3.1 · 4.6 · 5.7 — свойство ДЕРЖИТСЯ, негоден прибор. ⇒ **зелень приёмки была везением, а не замером**, и цена дефекта не в этом тесте, а в том, что он красит лендинг чужой зоны и заставляет её доказывать невиновность. **Форма лечения названа сессией:** мерить половины ВПЕРЕМЕЖКУ (A-B-A-B, брать медиану) либо нормировать на опорный замер той же секунды — тогда общая нагрузка сокращается по-настоящему, а не по предположению. ⚠ Родня ряда про «пин, флейковый на мутанте, измеряет пустой сценарий», но здесь хуже: флейк на ЗДОРОВОМ дереве | бэкенд | скоро | пин краснеет только на настоящей квадратичности: посадка «вернуть квадратичный `window()`» краснеет им, а десять прогонов полной батареи подряд — зелёные | найдено `textmachine-main-c5` на входной батарее 17.09, пере-снято оркестратором пятью изолированными прогонами | -| 499 | ⛔ **ИНГЕСТ EPUB ЧИТАЕТ СТРОКУ СТИХА КАК АБЗАЦ ПРОЗЫ.** ⚠ **ИСПР. 18.09 — ПРЕЖНИЙ ЗАГОЛОВОК НАЗЫВАЛ НЕ ТОТ ПРЕДИКАТ, и опроверг его ИСПОЛНЕНИЕМ полигон (`textmachine-b5`), а не рассуждение.** Было: «вложенность блочной цитаты умножает переносы». Неверно: `backend/internal/chunk/chunker.go:470`=`func splitParagraphs(chapter string) []string` делит по `\n\n` и **отбрасывает пустое** ⇒ избыток переносов безвреден, вложенность в десять уровней роли не играет. **Настоящий механизм виден в коде:** `blockTags` эмитит `"\n\n"` и на открытии (`backend/internal/chunk/ingest.go:1157`=`body.WriteString("\n\n")`), и на закрытии (`:1185`), тогда как `
` даёт одиночный `\n` (`:1152`=`body.WriteByte('\n')`) ⇒ **вложенный `

` внутри `

` — разметка издателя для СТРОКИ стиха — получает АБЗАЦНЫЙ разрыв**, и эпиграф из четырёх строк приезжает переводчику четырьмя независимыми абзацами прозы. ⭐ **Лечение по роду (формулировка полигона):** не «меньше переносов», а КЛАСС цитатно-стиховых контейнеров, внутри которых вложенный `

` даёт строчный перенос, а сам контейнер — абзацный; стих приезжает одним абзацем из строк, состав класса — данные. ⚠ **Побочный эффект назван автором находки ДО приёмки:** многоабзацная цитата ПРОЗЫ тоже склеится в один абзац с переносами — меньшее зло, чем разорванный стих, но решение владельца предмета. ⛔ **Альтернатива «абзац не кончается знаком конца предложения ⇒ это строка» ОТВЕРГНУТА обеими сторонами:** знаки препинания у пар разные, это ветвление по языку, которого инвариант не допускает. ⚠ Дефект живёт только на EPUB-парах: китайская боевая пара идёт из txt и потому не проявляет его — он прожил незамеченным именно поэтому. ⚠ Адрес `ingest.go:614` из пинга полигона относится к ДРУГОМУ срезу (основной чекаут против воркри); в этом ряду адреса мои. `backend/internal/chunk/ingest.go:978`=`"h5": true, "h6": true, "li": true, "blockquote": true` — каждый блочный тег отдаёт перенос, и набор существует НАМЕРЕННО: соседний докблок (`:975`=`survives extraction; without them a whole chapter collapses into one paragraph`) объясняет, что без него глава схлопывается в один абзац. ⛔ Но у книги, где эпиграф набран ВЛОЖЕННЫМИ `blockquote` — по одному на типографскую строку, — переносы умножаются на глубину. **Замер пере-снят оркестратором своим прибором и сошёлся с зонным знак в знак: 382 тега `blockquote` в 12 документах из 126, максимальная вложенность 10.** Следствие на входе модели (замер сессии по всем 71 главам движка): непустых абзацев 11 203, из них **726 не кончаются знаком конца предложения** — 6.5 % абзацев и 1.7 % рун, то есть обрывки фразы; прогонов из 8 подряд переносов — 101, из 16 — 66, встречается до 24. Обычная проза при этом ЦЕЛА (контроль: 2532 абзаца в крупнейшей главе) ⇒ предмет именно вложенность, а не блочность. ⛔ **Цена — качество перевода:** модель получает стих как набор обрывков, разделённых восемью пустыми строками, и сшивает их по своему усмотрению. ⚠ **Сегодня это обойдено ПРАВИЛОМ В ПРОМТЕ английской пары («собери такой блок в целую фразу»), и сессия сама назвала это КОСТЫЛЁМ вслух** — обход прячет цену, пока нет механизма. Лечение по роду: вложенная цитата есть ОДИН блок, а не N; условие снятия костыля — пин, краснеющий на вложенности ≥2 | бэкенд | скоро | вложенный `blockquote` даёт один перенос, а не по одному на уровень; пин на книге с вложенностью ≥2; правило-костыль снято из промта и названо в отчёте | замер `textmachine-main-c5` 17.09, пере-снят оркестратором (382/12/10 — сошлось) | +| 499 | ⛔ **ЭПИГРАФ ПРИЕЗЖАЕТ РАССЫПАННЫМ НА ТИПОГРАФСКИЕ СТРОКИ — НО ВИНОВАТ НЕ ДВИЖОК, А КОНВЕРТЕР ИЗДАТЕЛЯ; И ВТОРАЯ ФОРМА ЯВЛЕНИЯ ДВИЖКУ ВООБЩЕ НЕ ВИДНА.** ⚠ **ЧЕТВЁРТАЯ РЕДАКЦИЯ 18.09. Три предыдущие называли РАЗНЫЕ механизмы, и все три были неверны** — это носитель того, как дорого обходится объяснение вместо замера: (1) «вложенность блочной цитаты умножает переносы» — опровергнуто: `backend/internal/chunk/chunker.go:470`=`func splitParagraphs(chapter string) []string` делит по `\n\n` и ОТБРАСЫВАЕТ пустое; (2) «`

` внутри `

` есть строка стиха» — опровергнуто ЗАМЕРОМ по самому epub ДВУМЯ независимыми приборами (сессия `textmachine-main-c5` разбором 126 документов и советчик оркестратора): тегов `

` внутри цепочек `blockquote` **НОЛЬ**; (3) «это стих» — тоже неверно. ⭐ **ЧТО В МАТЕРИАЛЕ НА САМОМ ДЕЛЕ:** цепочек `blockquote` **88**, пустых 0, и **87 из 88 содержат ровно ОДНУ строку**, медиана длины **32 знака**; подряд идущие строки складываются в СВЯЗНУЮ ПРОЗУ эпиграфа, разбитую конвертером по ширине набора, а вложенность по CSS самого файла означает типографский ОТСТУП (`margin-left`), то есть след конвертера, а не структуру. `
` в книге **0**. ⇒ движок ведёт себя верно: он получает N абзацев и отдаёт N абзацев; беда в том, что издательский epub потерял границу между СТРОКОЙ и АБЗАЦЕМ ещё до нас. ⛔ **ВТОРАЯ ФОРМА, найденная только 18.09 и невидимая прежним замерам:** гимны и песни В ТЕЛЕ глав набраны не цепочками, а **курсивными абзацами по строке** — **154 строки в 32 блоках**. Движок их не видит ВООБЩЕ, потому что курсив до чанка не доезжает (ряд **500**): в обходчике ингеста ноль веток для инлайн-разметки, текст пишется безусловно (`backend/internal/chunk/ingest.go:1230`=`body.WriteString(text)`). ⇒ **суммарно ≈242 строки, а не 88.** ⛔ **ЛЕЧЕНИЕ — НЕ ДЕТЕКТОР, и это решение консилиума:** движок НЕ выносит вердикт «это стих»; он ДОНОСИТ СТРУКТУРУ на провод (образец — `pandoc`), а решает модель. Вторая форма закрывается починкой курсива из ряда 500 сама собой: прогон коротких помеченных абзацев модель опознаёт как стих без всякого порога. Первая форма закрывается языко-слепым правилом пары «несколько коротких абзацев подряд, каждый продолжает предыдущий, и ни один не реплика». ⛔ **ОТВЕРГНУТО ДВАЖДЫ и повторно подтверждено замером:** эвристика «абзац не кончается знаком конца предложения ⇒ это строка» есть ветвление по языку (знаки у пар разные) И вдобавок меряет не то: разбор «726 рваных абзацев» дал заголовки, обрывки оглавления и реплики, оборванные тире, — стиха среди них НЕТ. ⚠ Числа двух приборов не складывать: 726 снято регекспом сессии, 573 — прибором советчика на другой популяции. ⚠ Адрес `ingest.go:614` из полигонного письма относится к ДРУГОМУ чекауту (ветка `polygon` в `textmachine`), здесь `blockTags` на `:978` — различие деревьев, не ошибка автора | бэкенд | скоро (идёт ОДНИМ паком с рядом 500: носитель обеих форм — инлайн-разметка, и чинятся они одной правкой ингеста) | структура исходника доезжает до модели разметкой, а не теряется: курсивный спан приходит помеченным, прогон коротких строк опознаваем без порога и без ветвления по языку; пин на сквозной путь «разметка в epub → маркер в чанке → тег в экспорте», а не два пина по краям | замер `textmachine-main-c5` 17.09, ПЕРЕ-СНЯТ и опровергнут дважды 18.09; четвёртая редакция стоит на двух независимых разборах epub плюс замере оркестратора по коду ингеста | | 500 | ⚠ **ПОДОЗРЕНИЕ, А НЕ ЗАМЕР: ИНГЕСТ EPUB, ПОХОЖЕ, ТЕРЯЕТ КУРСИВ ЦЕЛИКОМ — и носитель курсива тут НЕ инлайн-тег.** `backend/internal/chunk/ingest.go:1085`=`func extractXHTML(data []byte) (string, []RubyReading, error)` пишет текстовые токены; разметка следа не оставляет. Для художественного перевода курсив несёт эмфазу, мысль героя, чужеязычное слово и эпиграф — авторское средство, и восстановить его данными нельзя: его нет на входе. ⛔ **Разметка честная: сквозного замера «курсив не доезжает до чанка» НЕ делал никто** — ни сессия, ни оркестратор; сделано только чтение кода и счёт спанов. ⚠ **И поправка к МЕХАНИЗМУ, снятая оркестратором: `` — 0, `` — 0.** Курсив в этой книге несёт **CSS-класс**, и счёт спанов расходится по популяциям: у оркестратора 4842 (`class="…ital…"`), у советчика сессии 4863. ⇒ формулировка «инлайн-теги не оставляют следа» описывает не тот носитель, и починка «читать ``/``» не сработала бы вовсе: читать надо класс. Прежде чем строить — сквозной замер на одном документе: доезжает ли хоть один курсивный спан до текста чанка | бэкенд | скоро (сперва ЗАМЕР, потом решение) | предъявлен сквозной замер «курсив на входе → курсив в чанке» с названным знаменателем; по его итогу — ряд на починку либо закрытие с доводом | подозрение сессии `textmachine-main-c5` 17.09; механизм поправлен оркестратором (класс, а не тег) | | 501 | ⚠ **АНГЛИЙСКАЯ ОСЬ ПОЛИГОНА СНЯТА НА СПЛОШНОМ БЛОКЕ: ИСКАЖЁН АБСОЛЮТНЫЙ УРОВЕНЬ, КОНТРАСТЫ АРМОВ ЦЕЛЫ.** ⛔ **Разметка: это утверждение ПОЛИГОНА (`textmachine-b5`) о собственной работе, оркестратором НЕ пере-снятое**; носителем оно стало потому, что на этих числах стоят промты боевой английской пары — то есть предмет ратификаций, а не внутреннее дело зоны. Английский экстрактор полигона (`pair_en.raw_text`) сносил из epub теги регексом вместе с `

`: `eval/dovodka/podacha.py:19`=`11 447 абзацев, медиана 62 знака` — модель получала сплошной блок вместо одиннадцати тысяч абзацев. **Затронуты ВСЕ 273 английские клетки**, а не отдельные армы: восстановленную подачу получает единственный арм `RA` (`eval/dovodka/rol.py:162`=`RESTORE_ARMS = ("RA",)`), и он НЕ КУПЛЕН — 0 клеток ⇒ **цена дефекта не измерена ни разу**. ⭐ **Но контрасты держатся, и дефект был обойдён ОСОЗНАННО, а не пропущен:** вход у всех армов одинаково плоский, и довод автора стоит в самом коде (`eval/dovodka/rol.py:126`=`RA / RN — эффект ПОДАЧИ`; парная строка выше даёт `RN / ZP` как эффект ПРОМТА при одинаковом входе) ⇒ сравнение армов между собой чисто, искажён общий УРОВЕНЬ, на котором стоят все. **Уцелевает:** контрасты промтов и топологий (`RB > RC > RN`), вендорские сравнения, основание выбора формулировки владельца. **НЕ уцелевает:** любое утверждение об АБСОЛЮТНОМ качестве английского текста, о вёрстке, об абзацном строе и длине абзаца — и любой перенос английского числа на пару, где исходник абзацы не терял. **Два числа боевых промтов en-ru стоят на этом и помечены в доке зоны в месте цитирования:** вес ролевого промта 1.50/1.13 и «редактор на английском покупает» 1.22 против 2.78 ошибок на единицу — оба судейские, на английской панели, при разрешении прибора `eval/dovodka/KONSILIUM-22-08.md:159`=`разрешение 1.63 места на китайском` против 2.15 на английском. ⛔ **Починка экстрактора ретроактивно обнуляет адресацию купленного:** uid единицы есть `sha1(source.strip())`, любая правка пробелов переименовывает ВСЕ единицы — 289 оплаченных клеток на $2.64 теряют адресата вместе с судейскими ответами; поэтому диспозиция зоны (`eval/dovodka/KONSILIUM-22-08.md:141`=`Починка экстрактора абзацев`) велит чинить НА ПОДАЧЕ (`eval/dovodka/podacha.py:203`=`def restore_paragraphs`), сохраняя uid. ⚠ **Не путать с рядом 499:** там дефект ДВИЖКА (`

` внутри `

` читается как абзац прозы), здесь — дефект ПОЛИГОННОГО экстрактора (регексп снёс `

`); одной правкой не чинятся и в один ряд не сливаются. ⚠ **Третья оговорка автора, без которой ряд придётся переписывать:** после починки ингеста движка старые полигонные числа станут НЕСОПОСТАВИМЫ с новыми движковыми — они сняты на разном тексте, и следующая смена иначе сравнит несравнимое. Китайская пара не затронута вовсе: исходник txt, абзацы не терялись, арма `RA` у неё «нет и быть не может» | полигон (носитель — оркестратор) | скоро (гейт: перед любым ПЕРЕНОСОМ английского числа полигона в ратификацию) | ⚠ **КРИТЕРИЙ ПЕРЕ-РЕШЁН 18.09 ПО ВОЗРАЖЕНИЮ АВТОРА НАХОДКИ, и прежний был слабее:** я ставил закрытием «носить пометку», то есть документировал предмет бессрочно вместо того, чтобы его закрыть. Пометка не несёт МАСШТАБА — из неё нельзя узнать, искажение это два процента или полтора места, — и потому обесценивает чистые числа ровно настолько же, насколько защищает грязные. **Закрывается:** куплен арм `RA` (16 единиц, промт и жилец как у `RN`, отличие ТОЛЬКО в подаче) и контраст `RA/RN` даёт число цены подачи. ⛔ **Ограничение покупки, без которого она бесполезна:** `RA` берётся на ТОМ ЖЕ жильце и с ТЕМ ЖЕ уровнем усилия, что `RN` (`eval/dovodka/rol.py:623`=`dspro: вендор-дефолт high` — оба на вендор-дефолте), иначе контраст смешает подачу с усилием и родит второй такой же ряд вместо закрытия первого. **До покупки** каждое английское число полигона в носителях несёт пометку «абсолютный уровень искажён, контраст чист, **величина искажения НЕ измерена**»; пометка ВРЕМЕННАЯ и снимается или уточняется величиной по итогу `RA/RN`. ⚠ **Цена — вход владельцу, а не санкция:** ≈$0.45–0.51 (сумма купленных клеток `RB` $0.5089 и `RN` $0.4454 — пере-снято оркестратором, сошлось до цента; ⚠ знаменатель расходится по провенансу: 16 ЕДИНИЦ против 32 ФАЙЛОВ, у каждой единицы есть спутник `.ERROR.json`, и медиана по файлам вчетверо ниже медианы по единицам). ⚠ **История оси несёт 402, но единиц НЕ теряла — пере-снято оркестратором 18.09 по артефактам:** английских записей об ошибке 52, ненулевых по цене среди них 0, по роду — 44 × `APIStatusError 402 Insufficient` (упор в баланс провайдера), 5 × `RateLimitError 429`, 2 × `APIConnectionError`, 1 × `BadRequestError 400`. ⛔ **Но у КАЖДОЙ ошибочной единицы есть та же единица с содержимым** (пересечение uid: `RB` 16/16, `RC` 16/16, `RN` 14/14, `RGT` 5/5), потерянных единиц НОЛЬ, и у всех тринадцати армов ровно по 16 единиц с содержимым ⇒ риг ретраил и добирал состав полностью. ⇒ **опасение «покупка потребует второго захода» замером НЕ поддержано**, а требование считать контраст по пересечению непустых остаётся верным по другой причине: у `RN` одна из 16 единиц несёт ПУСТОЕ содержимое (класс «доставленная пустота», ряды 414–415). ⚠ Провенанс счёта: «273 английские клетки» есть ФАЙЛЫ (включая спутники `.ERROR.json`), единиц с содержимым по 16 на арм. Зона полигона выведена из скоупа словом владельца 11.09 ⇒ покупку решает ОН | утверждение `textmachine-b5` 18.09; все пять адресов пере-проверены оркестратором | | 502 | ⛔ **УПОР В БАЛАНС ПРОВАЙДЕРА НЕ ИМЕЕТ СОБСТВЕННОГО ИСХОДА: ПРОВОД КЛАССИФИЦИРУЕТ ЕГО ВЕРНО, А ОПЕРАТОР ПОЛУЧАЕТ ОБЫЧНОЕ ТЕРМИНАЛЬНОЕ ПАДЕНИЕ СТАДИИ.** ⚠ Разметка честная: **снято ЧТЕНИЕМ КОДА, живого 402 не исполнял** — движок в этом состоянии не гонялся. **Что построено и работает:** `backend/internal/llm/httpllm.go:691`=`func retryableStatus(status int, body []byte) bool` есть ЕДИНСТВЕННЫЙ источник раздела «повторять/терминально» и стережётся дрейф-гардом; 402 терминален по умолчанию (`:698`=`terminal 4xx (incl. 402 Payment Required)`), а 429 с телом об исчерпанных кредитах тоже терминален — узкий набор маркеров `:710`=`insufficient_quota` / `quota_exceeded`, и довод стоит рядом: повтор под удержанной резервацией жжёт только время, потому что «the operator must top up». ⇒ **денег на пустые ретраи мы не тратим, и это важно сказать первым.** ⛔ **Дыра выше провода:** типизированный `HTTPStatusError` (`backend/internal/llm/httpllm.go:717`) не покидает пакет `llm` — 7 упоминаний, ВСЕ в `internal/llm`, в конвейере ноль; ошибка едет наверх обёрнутым текстом (`%w`), а перечислимых причин в `backend/internal` **43**, и **ни одна** не про кредиты провайдера (контроль: строк со словом `reason` — 943; единственный хит шаблона «причина + кредиты» — посторонний комментарий `backend/internal/pipeline/rebill.go:269`). ⇒ «у провайдера кончились деньги» и «стадия упала терминально» на уровне ИСХОДА неразличимы; тело ответа провайдера доезжает до человека только внутри текста обёрнутой ошибки. ⛔ **Почему это гейт готовности, а не гигиена:** предстоят ДВЕ книги по 20 глав ПАРАЛЛЕЛЬНО, и прогон, встретивший 402 на середине, останавливается наполовину купленным; оператору нужно за секунды отличить «пополни счёт» от «движок сломался», а сегодня он это читает из текста ошибки. ⚠ **Событие не гипотетическое:** на английской оси полигона 44 записи об ошибке из 52 — ровно `APIStatusError 402 Insufficient` (ряд **501**), то есть провайдер уже упирался в баланс, и заметили это постфактум, разбирая артефакты. | бэкенд | **скоро (кандидат в блокеры ратификации готовности к эксперименту двух переводов)** | у исчерпания кредитов провайдера есть НАЗВАННЫЙ исход, отличимый от обычного терминального падения стадии, и он виден оператору прогона; пин краснеет, когда тело с 402 превращается в родовую причину | замер оркестратора 18.09 чтением кода; повод — находка `textmachine-b5` о 44 отказах по балансу на английской оси |