textmachine/docs/BACKEND_CUT_CALLS_SESSION_PROMPT.md

44 KiB
Raw Blame History

Пак бэкенда: «ВЫЗОВ, КОТОРЫЙ ОБОРВАЛИ МЫ»

1. Какая проблема и что решит твой результат

Движок отправляет запрос провайдеру, тот принимает его и генерирует — а мы по своему же дедлайну перестаём ждать и рвём соединение. Провайдер об этом не знает и работу не отменяет. Дальше движок выбрасывает единственную улику: internal/llm/httpllm.go:409data, _ := 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 держит 7576 tok/s (n=139). Грант редактора 16 000 ≈ 213 с; реген удваивает его до 32 000 ≈ 427 с и НЕ трогает время, а attempt_s равен 240. Потолок по времени 1721 тыс. токенов; максимум, когда-либо доставленный регеном, — 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_tokensmaxTokensForAttempt удваивает бюджет по оси попыток (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.6-трис ⚠ ИСПР. 10.09 ПО ИТОГУ ПРИЁМКИ — ДВА МЕСТА ЭТОГО ПАКА ОКАЗАЛИСЬ НЕВЕРНЫ, И ОБА МОИ

Приёмка предъявила два расхождения между буквой пака и тем, что ратифицировано. Исправляю здесь, а не в письме, чтобы читатель пака не мерился ложной буквой.

  1. §4.2 писал: «доставлен + наш дедлайн (с заголовками И БЕЗ) ⇒ сумма через settleUSDForCutCall». Это отменено МОИМ ЖЕ поздним заказом — разведением предикатов: деньги считаются по answered && afterHeaders, а до заголовков доставленный обрыв книжится нулём. Довод был замером и он в силе: 3 из 25 отменённых в полёте сеттлили оценку за запрос, которого не видел ни один хендлер, — переплата ЧИТАТЕЛЯ, единственное запрещённое D39.196 п.2а направление. ⇒ буква §4.2 про «и без» недействительна; действует разведение предикатов. Остаток (доставленный обрыв до заголовков уходит в ноль) — строка бэклога, не дефект исполнения.
  2. §4.7 писал: «Anthropic-адаптер — ВНЕ пака ЦЕЛИКОМ». Отменено моей же ратификацией D39.231 п.2: одна строка CheckRedirect внесена, потому что через редирект уходил x-api-key, а net/http кастомный заголовок через смену хоста не срезает. Приёмка права, что это выход за объявленное сужение — и права также в том, что цена (тест и мутация) ровно та, которой сужение и мотивировалось. ⇒ сужение §4.7 действует на всё, КРОМЕ утечки учётных данных.

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. Поля утверждаются ДВУСТОРОННЕ, иначе они декоративны: WhitespaceOnlytrue на теле "\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 mutations65 записей · 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 «что не удалось» · вопросы оркестратору.