# Пак бэкенда: «ВЫЗОВ, КОТОРЫЙ ОБОРВАЛИ МЫ» ## 1. Какая проблема и что решит твой результат Движок отправляет запрос провайдеру, тот принимает его и генерирует — а мы по своему же дедлайну перестаём ждать и рвём соединение. Провайдер об этом не знает и работу не отменяет. Дальше движок **выбрасывает единственную улику**: `internal/llm/httpllm.go:409` — `data, _ := io.ReadAll(...)`, ошибка чтения уходит в `_`, а обрыв распознаётся по РАЗМЕРУ. Тело, оборванное нашим дедлайном, маленькое ⇒ не отличимо от целого ⇒ не разбирается как JSON ⇒ объявляется «оплаченным 2xx с нечитаемым телом» и **ретраится** (`:433` терминален только для >16 МиБ; `:365` даёт один повтор). **Цена — один замер и одно наблюдение, и разница между ними важна.** `docs/experiments/23-editor-tier.md` §5: латентности ровно `2×attempt_s`, **$0.475893 = 23.8 %** цены руки — это САМИ четыре строки; юниты целиком, вместе с их попытками 0, — **$0.689215 = 34.5 %**. ⛔ Источник пишет дословно «две законные цифры, и путать их нельзя» и регистрирует эту путаницу СВОИМ дефектом №11; не повторяй её, называя одно число двумя долями. Живой прогон 08.09: два редакторских вызова, `unexpected end of JSON input`, **$0.407606 из $1.259217 = 32 % бюджета**. ⚠ **Что это ИМЕННО таймауты — не измерено, а выведено по совпадению подписи** с замером выше: сырого леджера того прогона в дереве нет (`.db` новее 07.09 — 0 при 163). Не опирайся на второе число как на улику класса. **Корень — НЕ транспорт, а дедлайн против оси регенерации.** DeepSeek держит 75–76 tok/s (n=139). Грант редактора 16 000 ≈ 213 с; реген удваивает его до 32 000 ≈ **427 с** и НЕ трогает время, а `attempt_s` равен 240. Потолок по времени 17–21 тыс. токенов; максимум, когда-либо доставленный регеном, — **19 354 = 60 % гранта**, и **13 доставок из 17 уложились бы в исходные 16 000**. ⇒ ось эскалации сегодня фиктивна: резервация бронирует недостижимое и съедает headroom под книжным потолком (строка **369**). **Что покупает твой результат.** (1) Мы перестаём покупать одну генерацию дважды. (2) Оператор и читатель видят, какие деньги — оценка, а не факт. (3) Эскалация становится настоящей: правильный дедлайн делает выданный бюджет достижимым. ## 2. Зона и git **Зона записи — `backend/`** плюс своя секция в `docs/PROGRESS.md` (это ЕДИНСТВЕННОЕ исключение из «`docs/` — зона оркестратора»). Чужие зоны (`platform/`, `frontend/`, `eval/`, `books/`) не трогаешь ни строкой; расхождения — пингом. **Ты НЕ коммитишь**: дерево готовишь и передаёшь на лендинг. Никаких `git add -A`, `reset --hard`, переписывания истории. `.env` не читать никогда. ## 3. Карта чтения — ЗАКОН, дальше только по её ссылкам 1. `docs/BACKLOG.md` — строки (⚠ «строка N» здесь — **ID в первой колонке, а не номер строки файла**: `sed -n '360p'` даст пустоту, греп `^| 360 |` — нужный ряд) **360** (несёт СОШЕДШУЮСЯ конструкцию лечения целиком), **369** (ось эскалации), **78** (движковая половина `PD-441`: публикация оценочных строк). Греп `^| 360 |`. 2. `docs/experiments/00-provider-quirks.md` — **канон велит читать ПЕРЕД правкой адаптеров и вызовов.** ⚠ Класса, который ты чинишь, там НЕТ (замер: `early-200`/«empty lines»/«10 minutes» — 0 хитов) — это пробел, и он пингом полигону, не твоя правка. 3. `docs/research/21-llm-transport-survey.md` — §Q6 и **ЭРРАТА ОРКЕСТРАТОРА 08.09 в шапке**: обзор неверен дважды, и обе половины вместе полтора месяца держали неверный дедлайн. Читай эррату ПЕРЕД §Q6. 4. `docs/architecture/15-money-path.md` — денежный путь; леджер есть НИЖНЯЯ граница. 5. `backend/cmd/tmmutate/main.go` — шапка целиком (контракт мутационного инструмента) + `backend/Makefile`, цели `battery` и `mutations`. Ратифицированное, нужное по существу, вложено сюда и похода в журнал решений НЕ требует: - ⛔ **`D39.230` п.1 — СЛОВО ВЛАДЕЛЬЦА, и у него есть УСЛОВИЕ.** Оценка за оборванный нами вызов списывается с читателя. **Но пометка — часть решения, а не украшение:** строка обязана нести свой класс и флаг `estimated=1`, а счёт и сумма оценочных строк — публиковаться машинно-читаемо рядом с `committed_usd`. Списать с человека непроверенную оценку без видимой пометки — НЕ то, на что дано слово. - **`D39.230` п.2** — платная проба вендорского баланса ОТКЛОНЕНА владельцем. ⇒ вопрос «биллит ли вендор оборванное» осознанно ОТКРЫТ; оценка есть верхняя граница под гипотезой «вендор досчитывает». Проб не проектировать и не предлагать. - **`D39.230` п.3** — ожидание провайдера до ~20 минут РАЗРЕШЕНО. Сверху не убьёт ничто: `RuntimeMaxSec`/ `WatchdogSec` — 0 хитов при 16 файлах с systemd-свойствами, у движка дедлайна нет. - **`D39.216`** — где форма не тянет, её развязывают или переписывают; «дешевле подпереть» доводом НЕ является; второй носитель одного знания не заводится (п.3б). - **`D39.183`** — запрет править тесты «чтобы прошло» про МОТИВ; правка от ЗАКАЗАННОЙ смены поведения — обслуживание, но объявляется отдельным пунктом отчёта. - **`D39.202`** — прибор, честно отвечающий на СВОЙ вопрос, читается как ответ на другой. Ноль и «не смотрели» неразличимы без контрольной величины. ## 4. Состав и разметка свободы ### 4.1 Признак: граница денег — ДОСТАВКА запроса, а не статус-строка — **делай РОВНО так** ⛔ **Доктрина «2xx ⇒ провайдер уже списал» (`httpllm.go:428`, `stagerun.go:610`) ЛОЖНА для нашего боевого провайдера.** DeepSeek документирует ранний `200` с пустыми строками, пока запрос ждёт планирования, и закрытие соединения, если инференс не начался за 10 минут; в наших логах пустое тело при `200` видно буквально (`response="\n"`, **8 строк из 617** «llm exchange» под `books/gu-zhenren/` — счёт мой). ⚠ **Но НЕ бери эту строку как улику self-cut: она пришла на 91-й секунде при дедлайне 240 — ушли не мы.** Настоящие self-cut в том же файле лежат ровно на **240.0 с** — четыре трейса в `probe-4axes/logs/p7-off.log` ⚠ (счёт опровергателя приёмки, оркестратором НЕ пере-снят; пере-сними сама, если будешь на них опираться). Разница и есть предмет пака: одна подпись, два разных класса, и различает их только признак §4.1. ⇒ `200` значит «принят», не «сгенерирован», и ветвь `No 2xx ever arrived` на DeepSeek недостижима по построению — все наши обрывы прятались под `decode_error`. **Единственный факт, общий для всех этих случаев: запрос ДОСТАВЛЕН, и ушли МЫ.** Он фиксируется без байта на проводе — `httptrace.ClientTrace{WroteRequest, GotFirstResponseByte}` в `attempt()`. Различение проверено исполнением на клиенте той же сборки: | случай | `WroteRequest` | `GotFirstResponseByte` | |---|---|---| | доставлен, заголовки держат, наш дедлайн | `true` | `false` | | TLS-рукопожатие висит — НЕ доставлен | `false` | `false` | | ранний `200` + `"\n"`, наш дедлайн | `true` | `true` | **Заказ:** (а) ошибка `ReadAll` больше не выбрасывается; (б) заводится типизированная `AttemptTimeoutError{Delivered, AfterHeaders, BytesRead, WhitespaceOnly, Elapsed}`; (в) исходы — **перечислены ниже поимённо, свой набор не собирай**. ⛔ Обёртывание обязано СОХРАНИТЬ `errors.Is(err, context.Canceled)` для кодов выхода — иначе стоп прогона получит чужой exit-код. **ПОСЛЕ 2xx — пять исходов:** | исход | признак | вызовов | деньги | чанк | |---|---|---|---|---| | целое, JSON ок | `readErr == nil` | 1 | usage | как есть | | целое, JSON битый | `readErr == nil`, разбор упал | **2** (кап 1, как сегодня) | оценка | флаг | | **self-cut** | `readErr != nil`, дедлайн `attemptCtx`, родитель ЖИВ | 1 | `settleUSDForCutCall` | флаг; резюм — **0** вызовов | | **отмена РОДИТЕЛЕМ** (стоп, Ctrl-C) | `readErr != nil`, `ctx.Err() != nil` | 1 | `settleUSDForCutCall` | ⛔ своя причина `cancelled`, НЕ общий флаг «нужен человек»; резюм — 1 вызов на ТОМ ЖЕ бюджете | | разрыв соединения | `readErr != nil` иначе (`ErrUnexpectedEOF`, h2 stream) | 2 | `settleUSDForCutCall` | на исчерпании — **инфра-пауза, НЕ флаг** | **ДО заголовков — четыре:** self-cut доставленный (1 вызов, флаг, резюм 0) · отмена родителем доставленного (1, деньги те же, резюм 1 на том же бюджете) · разрыв после доставки (2, деньги те же, инфра-пауза, резюм 1) · **НЕ доставлен** (до `max_attempts`, **$0**, освобождение резерва, резюм 1 — это первая покупка, а не перекупка). ⚠ **`TestBilledDecodeErrorReBillCap` не меняется** — у него `readErr == nil`, это второй исход таблицы. ### 4.2 Диспозиция: не ретраить то, что оборвали мы — **делай РОВНО так** - **Доставлен + наш дедлайн** (с заголовками и без) — класс `attempt_timeout`, **чекпойнт + флаг, БЕЗ ретрая**, сумма — через `settleUSDForCutCall`. - **НЕ доставлен** (дедлайн до `WroteRequest`) — единственный случай, где «ничего не куплено» верно по построению: остаётся как сегодня (retryable, на исчерпании освобождение резерва, инфра-пауза). - ⛔ **ОТМЕНА РОДИТЕЛЕМ доставленного вызова** (стоп прогона, Ctrl-C) — **это тот самый случай, с которого начался заказ (строка 331), и диспозиция у него ДРУГАЯ.** Деньги — та же `settleUSDForCutCall` (ровно то, что просит `PD-441` — по-русски: «сеттлить оценку и при отмене вызова, который успел уйти, различая по ФАКТУ ухода»), но чанк получает **СВОЮ причину `cancelled`, а не общий флаг «нужен человек»**: вызов был здоров, остановил его ЧЕЛОВЕК. ⛔ **Это НЕ «флага нет».** Дыры без пометки не должно быть ни в один момент: пока прогон не резюмирован, диспозиция чанка не `ok`, и экспорт показывает `flagged(cancelled)` — читается как «остановлено, резюм пере-сделает», а не как «нужен человек». Флаг, врущий о ПРИЧИНЕ, запрещён отдельно (`D39.93` п.2); невидимая дыра запрещена тем более. ⛔ **И резюм обязан пере-делать его на ТОМ ЖЕ `max_tokens`** — `maxTokensForAttempt` удваивает бюджет по оси попыток (`disposition.go:393-401`, `base << uint(attempt)`; петля берёт его из НОМЕРА попытки, `stagerun.go:147`), и здесь это ловушка: удвоение купит вдвое больший вызов за здоровье, которого никто не терял. ⛔⛔ **КАК это построить — задано, потому что наивный путь НЕВОЗМОЖЕН, и это проверено.** В сторе ровно три функции (`Reserve`, `Release`, `SettleWithCheckpoint`), деньги нельзя записать БЕЗ чекпойнта, а чекпойнт — ключ резюма (`ON CONFLICT (request_hash) DO NOTHING` ⇒ «Duplicate settle: book no new spend»). ⇒ «заплатить и пере-сделать на том же ключе» несовместимо по построению. **Развязка — в ПЕТЛЕ, а не в сторе:** индекс попытки и счётчик УДВОЕНИЙ разводятся. `maxTokens = base << escalations`, где `escalations` растёт только на `length`/`empty`; кап регенерации считает УДВОЕНИЯ, а не индекс. Тогда попытка N+1 берёт тот же бюджет, но НОВЫЙ хеш (`Attempt` — самостоятельное измерение) ⇒ свежие `reserve → call → settle`, `ON CONFLICT` недостижим, инвариант `committed == sum(checkpoints)` цел, стор не тронут. ⛔ **Стор и его инварианты не трогать** — упрёшься, это пинг. ⚠ И проверь побочное: `priceprojection.go` считает проекцию через `maxTokensForAttempt` («RETRIES ARE IN IT, AND THAT IS THE POINT») — смена оси двигает СМЕТУ. Двинулась — назови в отчёте числом. ⚠ **Видимое следствие, назови его в отчёте:** стоп прогона теперь ПЛАТИТ оценку за каждый летящий вызов (в редакторской волне это до восьми × ~$0.13). Слово владельца это покрывает («оборванный НАМИ» — мы рвём по его команде), но молча вводить это нельзя. - **Разрыв соединения ПОСЛЕ доставки** — один ретрай (кап ключуется на «доставлен», а не на «2xx»), на исчерпании **инфра-пауза, не флаг**: серия таких — мёртвый провайдер, и довод нулевой ветви про флаг-шторм здесь верен. ⛔ **Но деньги назвать обязательно, иначе граница течёт через эту дверь:** сегодняшний инфра-путь зовёт `releaseReservation` и даёт **$0**, а запрос был ДОСТАВЛЕН ⇒ идёт `settleUSDForCutCall` + класс `connection_lost`, и ТОЛЬКО потом пауза; резюм пере-делает на том же бюджете. Для post-header разрыва это сегодняшнее число, для pre-header — новое, и объявляется заказанной сменой поведения. ⚠ Довод против «оставить ретрай, он же 3 раза из 7 спасал»: правильный дедлайн даёт то же самое без монетки по цене полного счёта. Клапан на будущее — ДАННЫЕ (`tok_s_floor`), не код. ### 4.3 ⛔ ТРЕТЬЯ УТЕЧКА, которой нет даже в строке 331 — **делай РОВНО так** Сегодняшняя нулевая ветвь (`stagerun.go:634`) зовёт `releaseReservation` и **чекпойнта НЕ пишет**, ставя стадию `failed` ⇒ **резюм ПОКУПАЕТ ВЫЗОВ ЗАНОВО**. Убрать ретрай и оставить это значит закрыть класс наполовину: тот же платный вызов уходит другой дверью. **Заказ:** доставленный self-cut получает чекпойнт, и резюм его находит. **Предъявление именно этим:** тест утверждает, что после self-cut резюм на том же снапшоте **не делает НИ ОДНОГО HTTP-вызова**. Мутация: убрать запись чекпойнта — тест обязан покраснеть текстом про свежий вызов, а не про деньги. ### 4.4 Дедлайн считается от бюджета — **делай РОВНО так по форме, числа реши сам и обоснуй** `timeouts` получает `tok_s_floor`, `queue_slack_s`, `attempt_max_s`; дедлайн вызова = `clamp(queue_slack_s + max_tokens/tok_s_floor, attempt_s, attempt_max_s)`, считается в `attempt()` из `maxTokens` запроса. **`attempt_s` становится ПОЛОМ** ⇒ ни один существующий конфиг не теряет времени. Дефолт пола — **35.5 tok/s** (128 000/час, единственное опубликованное вендором число). ⭐ **Почему этот дефолт правильный, и это же твой тест:** он ПРОИЗВОДНЫЙ, и на черновике (8 496 токенов) даёт **238.95 с** — сегодняшние 240 с точностью до секунды. То есть нынешнее значение верно ровно для черновика и было перенесено на редактора слепо. ⛔ **Тест-тождество обязан пинить ФОРМУЛУ, а не число, и вот операционная форма — иначе его исполнят формально.** `assert derive(8496) == 238.95` негоден: это литерал, произведённый формулой, и через полгода его «починят» под круглые 240. Требуется: ожидание **вычисляется в тесте** из двух вендорских констант (час, 128 000) и гранта; **литералов `238`/`239`/`240`/`900` в тест-файле НЕТ** — предъяви грепом с контрольной величиной; не меньше ДВУХ грантов (8 496 и 32 000); отношение удвоения `derive(2N) − slack == 2·(derive(N) − slack)`; края `clamp`; пустое и нулевое поле дают ДЕФОЛТ, а не деление на ноль и не нулевой дедлайн. **Твоё — ЧИСЛА. Моё — ПРАВИЛО их вывода, и оно РОВНО так:** - `tok_s_floor` — НИЖЕ p10 наблюдаемой скорости модели по ≥100 строкам `request_log`, округление вниз объявлено (DeepSeek: p10 ≈ 67 ⇒ 50); - `queue_slack_s` — документированное ВЕНДОРОМ максимальное ожидание до инференса (DeepSeek — 600). ⛔ Меньше — только с доводом и ПИНГОМ: владелец сказал «готов ждать» именно про ~20 минут, и меньший слак меняет принятый им размен молча, в другую сторону; - `attempt_max_s` — слак + максимальный грант С УДВОЕНИЕМ / пол, округлённый вверх (DeepSeek: 600 + 32000/50 = 1240). ⛔ **И два механизма, без которых «новый провайдер без правки Go» не работает:** WARN на старте для провайдера БЕЗ `tok_s_floor`, чей стадийный грант с учётом удвоения выше ~8.5k («дефолт 35.5, дедлайн для 32 000 = 900 с, задай после первого прогона»), и валидационный тест `models.yaml`, ТРЕБУЮЩИЙ поле для таких провайдеров. ⚠ **Снапшот не двигается, и проверено это ПРЯМО, а не соседством:** `buildSnapshotID` (`internal/pipeline/snapshot.go`) перечисляет входы поимённо, и таймаутов среди них нет — читай сам список, а не грепай близость слов. ⛔ Прежняя редакция этого пака писала «`AttemptS` — 0 хитов из 120 файлов со `snapshot`»: замер был неверен в ЧИСЛИТЕЛЕ (хит 1 — `config/models.go`) и в ЗНАМЕНАТЕЛЕ (файлов 145), а вывод верен — правый вердикт по неправой причине, `D39.202`. Если твоя правка список входов меняет — СТОП и пинг: перекупка книг словом владельца не покрыта. ### 4.5 Пометка — УСЛОВИЕ решения владельца — **делай РОВНО так** Класс `attempt_timeout` и `estimated=1` едут в чекпойнт и `request_log`; `classify()` узнаёт класс на резюме; `paidtail` включает его в LostUSD. **И сверх того — то, ради чего дано слово:** счёт и сумма оценочных строк публикуются **машинно-читаемо рядом с `committed_usd`** (`D39.230` п.1; это же движковая половина `PD-441`, строка **78**). ⚠ **Поверхность назови точно: это тот JSON, который ЧИТАЕТ платформа (`tmctl status --json`), а не новый файл.** Формат — пингом платформе, не решением за неё. ⚠ Сегодня флаг `estimated` виден только человеку в `tmctl render.go`; платформа его не читает — ей нужно число, а не строка экрана. ### 4.6 Оператор не должен принимать ожидание за висяк — **реши сам, бери если по пути** С разрешённым ожиданием до ~20 минут INFO-лог говорит «calling model» и потом молчит. Дешёвая строка «жду провайдера: прошло X из Y» раз в N минут снимает это. ⚠ И назови в отчёте, что `waitForSettle` теперь ждёт до самого длинного летящего вызова — это принятая цена решения владельца, но она должна быть названной. ### 4.6-бис Мандат чистоты — **делай РОВНО так** Пак ПРОЕКТИРУЕТ: новый тип ошибки, три новых поля конфига, новая денежная семантика. ⇒ `D39.216` действует буквой: где нынешняя форма не выдерживает заказ, её **развязывают или переписывают**, а не обкладывают заплатой; «дешевле подпереть» доводом НЕ является; второй носитель одного знания не заводится (п.3б). Отступление законно, но объявляется С ЦЕНОЙ: почему заплата дешевле развязки и что именно она откладывает. ⚠ Нота требует этой строки в КАЖДОМ паке с проектированием и называет её отсутствие дефектом промта — она здесь не для полноты. ### 4.7 Чего в паке НЕТ — **не делай**, и это ОБЪЯВЛЕННЫЕ сужения - **Стриминг** — не спас бы ни одно из 19 замеренных событий; цена: `stream:true` в теле ⇒ `RequestHash` ⇒ перекупка книги, непроверенная байт-идентичность склейки, новый парсер, потеря usage-fidelity. Trip-wire обзора переписан: доля self-cut в спенде редактора ПОСЛЕ починки дедлайна. - **Платная проба вендорского баланса** — ОТКЛОНЕНА владельцем (`D39.230` п.2). Не проектировать. - **Адаптивный пол из леджера на лету** — прячет число от оператора, тащит состояние прогона в дедлайн. - **`max_attempts` и бэкофф** — другая ось, не трогать. - **Порог по латентности** («≈2×attempt_s») — совпадение; протухнет молча при смене `attempt_s`. - **Idle-timeout в non-streaming** — схлопывается в общий бюджет (§Q6 прав). - **Разбор обрывков тела.** - **Оценка по времени `elapsed × rate`** — догадка на догадке; оправдана только исходом пробы, которой не будет. - **Денежная сумма как YAML-ручка** — политику нельзя переключать молча конфигом; она за одной функцией. - **Строка 368 (`est_tokens` занижена в 5 раз)** — отдельный носитель, другой предмет. Наткнёшься — наблюдением в отчёт, не чини: два денежных класса в одном лендинге мы сегодня уже разводили. - **Anthropic-адаптер** (`provider_anthropic.go`) — ВНЕ пака целиком: поверхность deprecated (из стека убрана 04.07), боевой цены ноль, а тесты и мутации она бы добавила. Уходит строкой бэклога. ## 5. Мандат самопроверки ИСПОЛНЕНИЕМ — «перечитал сам» не считается 1. **Каждая гарантия предъявляется ИСПОЛНЕНИЕМ на фейк-сервере**, а не чтением. Пять исходов после 2xx и четыре до заголовков — **ДЕВЯТЬ фикстур**, и у каждой утверждается ЧИСЛО HTTP-вызовов, а не только исход. ⛔ **Счёт вызовов берётся у ФЕЙК-СЕРВЕРА** (сколько запросов он принял), а не счётчиком клиента: клиент считает свои намерения, сервер — факты. ⛔⛔ **И к КАЖДОЙ доставленной-и-потерянной фикстуре — ПАРНОЕ утверждение о РЕЗЮМЕ**, иначе конструкция вырождается в противоположную сторону: сессия может «пере-закрыть» — не пере-делывать НИЧЕГО доставленного — и остаться зелёной. Ожидания: self-cut → резюм **0** вызовов; отмена родителем и исчерпанный разрыв → **ровно 1 вызов на ТОМ ЖЕ `max_tokens`**; не доставлен → 1. ⛔ **Поля утверждаются ДВУСТОРОННЕ, иначе они декоративны:** `WhitespaceOnly` — `true` на теле `"\n"` и `false` на обрывке JSON; `Delivered` — обе стороны. И денежное утверждение на каждой строке: `estimated=1`, `cost_usd == estimate`, класс на месте; у «не доставлен» — `reserved_usd` вернулся в 0. 2. **Мутации — на копии, рецепт УЖЕ в цели `mutations`** (`backend/Makefile`); своего однострочника не пиши. Краснота читается ПО ТЕКСТУ падения: говорит ли он про сломанное тобой. 3. **Мутируй и СВОИ новые фикстуры.** ⛔ Особая осторожность: у тебя условные сообщения (пометка `estimated`, WARN про пол провайдера) — на каждое нужна фикстура, где оно ОБЯЗАНО прозвучать, И вторая, где обязано молчать. Тест, утверждающий только молчание, вакуумен. 4. **Субагенты РАЗРЕШЕНЫ ЯВНО** — и на охват, и на адверсариальный проход по ГОТОВОЙ работе. ⛔ **Направление — что уязвимо ИМЕННО в этом паке** (норма куплена замером 08.09: направленный проход нашёл шесть дефектов пака на ЗЕЛЁНОЙ батарее): **(1)** фикстуры, где «доставлен» и «не доставлен» случайно совпали — весь пак стоит на этом различении; **(2)** путь резюма: чекпойнт написан, но `classify()` его не узнаёт ⇒ перекупка возвращается тихо; **(3)** `errors.Is(context.Canceled)` после твоего обёртывания — коды выхода ломаются молча, И одновременно `errors.As` обязан НАХОДИТЬ твой тип: два утверждения в одной фикстуре, не в разных; **(4)** исход «отмена родителем» — его легко потерять, потому что он единственный, где деньги списываются, а флага НЕТ. 5. **Проверяемый артефакт** — таблица «находка → что сделано → ЧЕМ ПРЕДЪЯВЛЕНО» плюс столбец **«закрыт в N носителях из M»**, где M посчитан командой. 6. **BASELINE НА ВХОДЕ сними САМ** и сверь с моим: `make battery` → 19 `ok` · 0 FAIL · 4 «no test files» · 4 скипа; `make mutations` → **65 записей · 0 выживших · свип `0 of 276`**; `python3 docs/scripts/counts.py --check` → «Литералы сходятся». ⛔ **Печатай счёт СКИПОВ рядом с числом `ok`** — норма куплена 08.09: «20 пакетов ok» и «20 ok при 431 скипе» неразличимы, и приёмка так приняла механизм, ни разу его не коснувшись. Красное на входе — находка и пинг, а не твоя работа. ## 6. Оси ревью — характер «Код», плюс деньги и шов (ось вправе заменить с аргументом) Тест утверждает не то, что думает (`D39.171`): часы как способ синхронизации там, где предмет НЕ время, и ассерт по подстроке в общем буфере. Обе формы дают и ложное красное, и **тихо-зелёное** — оно дороже. **Деньги:** любое число из СЫРОГО леджера, а не из агрегата; леджер — нижняя граница. **Шов:** платформа биллит пользователю `committed_usd` и просила у движка публикацию оценочных строк (`PD-441`). Инвентарь её каналов — чтением ЧУЖОГО кода, не по памяти; правок в `platform/` НЕ делать. **Индустриальный первоисточник прежде велосипеда:** `httptrace` — stdlib; если решишь иначе, отвергни с доводом. ## 7. Записка-план ДО работы До первой правки — короткая записка в свою секцию журнала: что берёшь, в каком порядке, что считаешь рискованным. После работы — сверка комплектности против §4 МЕХАНИЧЕСКИ (таблицей), не перечитыванием. ## 8. Заявление = команда ⛔ **Эрудиция НЕ улика:** утверждение о коде, провайдере или чужой зоне грунтуется `file:line`, цитатой или замером — либо несёт пометку **«мнение»**. Каждое число и категорика — с командой, которой получены. Отрицательный замер печатается ВМЕСТЕ с контрольной величиной. Сверх этого: **перед отчётом сверь каждый клейм с результатом инструмента ЭТОЙ сессии**, а не с памятью и не с этим промтом — адреса §4 сняты 08.09 и могли уехать. **Последний абзац отчёта оказался планом или обещанием? сделай это СЕЙЧАС.** ⛔ Править тест/гейт, чтобы он прошёл, — НЕДОПУСТИМО; исключение — правка от ЗАКАЗАННОЙ смены поведения, и она объявляется отдельным пунктом. Сессия длинная — поставь себе интервальную самоверификацию субагентом против ЯВНЫХ критериев §13. ## 9. Эхо-протокол старта ДО работы — ≤10 строк **СВОИМИ словами**: скоуп · инварианты · что НЕ делаешь. Дословный пересказ промта подтверждает канал, но не понимание, и ошибку промта повторяет вместе с ним. Эхо — оркестратору по адресу из прямого канала (§12); канала нет — эхо первой секцией отчёта, работа продолжается. ## 10. Что НЕ удалось — обязательная секция отчёта Что не вышло, что не проверено, где сомневаешься. «Не проверено» и «опровергнуто» — РАЗНЫЕ исходы. ## 11. Канал вопросов и право сказать «этого делать не надо» Конфликт промта с кодом или доками — **пинг, а не интерпретация**: конструкция этого пака сошлась в консилиуме двух моделей, но её проверяли чтением, а не твоим прогоном. Считаешь пункт неверным — скажи с доводом, довод сильнее моей подписи. Решение владельца требуется, если работа упрётся в перекупку книг, в скоуп или в продукт. ## 12. Прямой канал Механизм — `CLAUDE.md` §«Связь между сессиями». Впиши СВОЙ блок в `/tmp/textmachine-channel` первым делом, чужие блоки не трогай. ⚠ Адрес из файла не доказывает, что сессия жива: сверься с `ListAgents`. Нужной роли нет или имя не отвечает ⇒ **канала нет, и это НОРМАЛЬНЫЙ случай: НЕ опрашивай сессии подряд** — вопрос секцией в отчёт, работа продолжается. ## 13. Критерий завершённости — по нему тебя примут Работу доводишь до конца САМ; приёмка идёт последней и вторым кругом твоей самопроверки не служит. Готово — это когда: - у КАЖДОГО пункта §4 есть исход: сделано · не делаю с доводом · пинг; - круги СОШЛИСЬ: последний проход не дал НОВЫХ находок, прежние закрыты таблицей; - **каждый класс закрыт со ЗНАМЕНАТЕЛЕМ «N из M»**, и M посчитан командой; - ДЕВЯТЬ фикстур §5.1 существуют, утверждают ЧИСЛО вызовов у СЕРВЕРА и несут ПАРНОЕ утверждение о резюме; пин резюма (§4.3) предъявлен мутацией; поля утверждены двусторонне; - тест-тождество §4.4 пинит формулу, а не константу; - числа сняты ПОСЛЕ последней правки, со счётом скипов рядом с числом `ok`; - **таблица мутаций ПОЛНАЯ, и выжившие названы поимённо** (выжившая мутация — находка, а не шум); - **дифф `^func Test` снят ИСПОЛНЕНИЕМ, а не памятью**, и назван прибор, которым считан; - всё живое — в ДЕРЕВЕ, а не в письме; - сказано явно: **«работа завершена, править не планирую»**. Без этой фразы пак считается идущим. ## Деньги **Пак $0.** Платных вызовов провайдерам НОЛЬ: все девять исходов предъявляются на `httptest`-сервере, формула дедлайна — арифметикой, резюм — фикстурой. ⛔ Платная проба вендорского баланса ОТКЛОНЕНА владельцем (`D39.230` п.2) — не проектировать и не предлагать. Кажется, что нужен платный прогон, — это стоп и пинг, а не вызов. ## Отчёт Своя секция в `docs/PROGRESS.md`: исход по каждому пункту §4 · таблица «находка → что сделано → чем предъявлено» · столбец «закрыт в N из M» · числа со счётом скипов · заказанные смены поведения отдельными пунктами · пинги (квирк-реестр — полигону; публикация оценочных строк — платформе) · §10 «что не удалось» · вопросы оркестратору.