# Пак бэкенда: «ДВИЖОК УЖЕ ЗНАЕТ — ДОВЕЗИ ДО ЧИТАТЕЛЯ» ## 1. Какая проблема и что решит твой результат Движок **вычисляет правду и пишет её себе в лог**, а тот, кто по этой правде принимает решение — владелец на экране подписи банка и оператор, читающий траты, — смотрит в другое место. Это один класс, а не три случая, и он сегодня стоит дороже всего остального в очереди. - **Полнота банка.** `internal/pipeline/terminologist.go:99-103` считает `Consolidated` · `Declined` · `Unanswered` · `BatchesDropped` · `ClassifyBatchesDropped`; `:582` предупреждает — «this bank is PARTIALLY consolidated — a budget cut a pass». В `Log`. А носитель, который читает платформа и с которого строится экран подписи, — `internal/pipeline/bankexport.go` (`BankExport`: `bank_version`, `book_id`, `total`, `signed`, `as_of`, `run_id`, `terms`, `proposed`) — полей полноты **не несёт вовсе**. Наблюдение платформы за живым потоком **04.09** (H10/H11, строка 199): консолидировано 38, **не спрошено ни разу 47**, срезано бюджетом 5. Владелец подписал бы такой банк как полный. - **Деньги.** `internal/pipeline/waverun.go:218` складывает в итог волны только `CostUSD` рендер-паса; `ClassifyCostUSD` (`terminologist.go:145`) в `TOTAL (this run)` не попадает. А разложение «за что заплачено» относит классификатор в `superseded`, потому что обе банк-роли адресуются ОДНИМ именем стадии и делят позицию `{глава, чанк, стадия}`. Замер прогона 08.09 по строкам леджера: терминологическая фаза стоила **$0.013608** — классификатор `$0.009013` плюс два рендер-батча `$0.002775` и `$0.001820`; в итог волны входит только рендерная половина, а разложение печатает `$0.004595 (2)` — ровно два последних. Оператор читает о своих деньгах два числа, не сходящиеся с сырым леджером — ровно тогда, когда решает, продолжать ли платить. **Что покупает твой результат.** (1) Владелец видит на подписи, что банк неполон, ДО того как подписал. (2) Числа отчёта сходятся с леджером. (3) Ни один байт снапшота, хеша запроса, ключа манифеста и версии встроенных данных не двигается — пак **$0** и ничего не перекупает. ## 2. Зона и git **Зона записи — `backend/`** плюс свой подзаголовок в секции «Бэкенд» `docs/PROGRESS.md`. Чужие зоны (`platform/`, `frontend/`, `eval/`, `books/`) не трогаешь ни строкой; `docs/` кроме своей секции — тоже. **Ты НЕ коммитишь**: дерево готовишь и передаёшь оркестратору (канон — `CLAUDE.md` §«Git-координация мультисессий»). Никаких `git add -A`, `reset --hard`, переписывания истории. ⚠ Заведи подзаголовок `#### Пак «движок уже знает»` внутри секции «Бэкенд» и пиши только под ним: файл коммичу я, и чужой текст уедет под моим сообщением. `.env` не читать никогда. ## 3. Карта чтения — ЗАКОН, дальше только по её ссылкам 1. `docs/BACKLOG.md` — строки **253** (обе половины) · **357** · **358** · **355** · **312**. Греп `^| 253 |`. Строка — носитель заказа; расхождение строки с этим промтом — пинг, не интерпретация. 2. `docs/architecture/18-bank-ontology.md` — **нормативная страница банковых проекций**: три правила дисциплины проекции и два класса дефектов. Ты добавляешь поля в проекцию банка ⇒ читаешь ДО работы. ⚠ Там же эррата 07.09 про класс A, заведённый сознательно. 3. `docs/architecture/15-money-path.md` — денежный путь: что чем считается и где леджер есть нижняя граница. 4. `docs/architecture/12-go-style-notes.md` — норматив формы («least mechanism», размер не критерий сплита, интерфейс объявляет потребитель, код английский). 5. `backend/cmd/tmmutate/main.go` — **шапка целиком** (контракт мутационного инструмента) плюс `backend/Makefile`, цели `battery` и `mutations`. Ратифицированное, нужное по существу, вложено сюда и похода в журнал решений НЕ требует: - **`D39.226`** — акт предыдущего пака этой зоны: что уже построено (узкий фильтр банка, пометка `NOT ASKED` в текстовом сайдкаре, каталог мутаций 259/48). - **`D39.225` п.10** — ⛔ **правило остановки, применённое к секции предложений сайдкара: четвёртый круг по ней НЕ открывается** (строка 353 — гейт «появится читатель»). - **`D39.104` п.1** — банк на проводе есть ЗАКОН для всех ролей независимо от статуса строки. Отсюда цена неполного банка: владелец подписывает то, что станет законом для всей книги. - **`D39.183`** — запрет править тесты «чтобы прошло» про МОТИВ; правка от ЗАКАЗАННОЙ смены поведения — обслуживание, но объявляется в отчёте. - **`D39.208` п.5** — фикстура, где две различные величины совпали, делает весь их класс невидимым. - **`D39.216`** — где нынешняя форма не выдерживает заказ, её РАЗВЯЗЫВАЮТ или переписывают, а не обкладывают заплатой; «дешевле подпереть» само по себе доводом НЕ является. Отступление законно, но объявляется с ценой: почему заплата дешевле развязки и что именно она откладывает. ⚠ Второй носитель одного знания не заводится: если правка требует держать две копии в согласии — развязка назрела. Это прямо про §4.1, где форму полей выбираешь ты. - ⛔ **Правило, купленное дважды подряд:** пин на УСЛОВНОЕ сообщение обязан иметь фикстуру, где оно ОБЯЗАНО прозвучать. Тест, утверждающий только молчание, вакуумен ровно там, где сообщение ложно. ## 4. Состав и разметка свободы ### 4.1 Полнота банка доезжает до того, кто подписывает — **состав РОВНО так, форма полей — реши сам** Три поверхности подписи, и правда есть только в логе: - **машинный сайдкар** `BankExport` (`internal/pipeline/bankexport.go`) — полей полноты нет; его читает платформа, и с него строится экран; - **текстовый сайдкар** стоп-таблицы (`internal/pipeline/mining.go`) — несёт пометку `NOT ASKED(…)` (1 хит), и это единственное место, где она есть; - **экран стопа на stdout** (`cmd/tmctl/render.go`, `renderSignatureStop`) — про пропущенного кандидата не знает: хитов `NOT ASKED` там **0**, при том что тот же рендерер УМЕЕТ печатать пер-строчные пометки (контроль — `invented`). **Заказ:** довези до ОБОИХ читателей то, что движок уже посчитал. Что именно нести — `Consolidated`, `Declined`, `Unanswered`, `BatchesDropped`, `ClassifyBatchesDropped` — и в какой форме (плоские поля, вложенный объект, одно суммарное «полон/неполон» плюс детали) **решаешь ты**; аргументируй в отчёте. ⛔ **Что не обсуждается:** (а) поля берутся из УЖЕ вычисленного — ПРОИЗВОДНАЯ от посчитанного (например бит «полон») законна и приветствуется, запрещён ПЕРЕСЧЁТ полноты вторым механизмом; (б) версию формата сайдкара (`tm-bank-v1`) НЕ бампать — платформа разбирает аллоулистом и новое поле игнорирует, это ПИНГ ей, а не поломка; (в) на stdout-экране оператор узнаёт о пропущенных кандидатах. ⛔ **Но НЕ обязательно пер-строчной пометкой, и вот почему — это ловушка вырожденной фикстуры.** `cmd/tmctl/render.go`: `bankStopStdoutCap = 20`, строки сортируются `reviewRank`, а у не-спрошенной строки `Dst` пуст ⇒ ранг **1000** ⇒ она уходит ПОСЛЕДНЕЙ и при ≥20 строках проваливается под кап. Фикстура на ≤20 строк будет зелёной, а на боевом банке оператор не увидит ничего. ⇒ **задано СВОЙСТВО** (оператор со стдаута узнаёт, сколько кандидатов не спрашивали и почему), **форму выбираешь ты** — сводная строка над таблицей или пер-строчная пометка плюс работа с капом; и фикстура обязана быть ШИРЕ капа; (г) секцию предложений сайдкара не трогать — по ней применено правило остановки (`D39.225` п.10, строка 353). ⛔ **«ОБА ЧИТАТЕЛЯ» = машинный сайдкар и stdout-экран.** Текстовый сайдкар стоп-таблицы уже несёт `NOT ASKED` и счётчиков полноты от тебя НЕ требует — трогать его в этом пункте не надо. ⛔⛔ **ПОСЧИТАННОЕ ДВИЖКОМ САМО НЕТОЧНО — не довози его буквально, иначе построишь ложную тревогу.** Три факта, которые смешивать нельзя: - **`BatchesDropped` (рендер-пас срезан)** — вот это и есть неполный БАНК: термины не консолидированы. - **`ClassifyBatchesDropped` (классификатор срезан)** — банк ПОЛОН, не уточнены ТИПЫ. Другой факт. - **`Unanswered`** — по собственному тексту предупреждения «counts them together with terms the role saw and did not answer», то есть СЛИВАЕТ «не предложено бюджетом» и «предложено, роль не ответила». ⛔ И предупреждение `terminologist.go` кричит «PARTIALLY consolidated» при `BatchesDropped > 0 **||** ClassifyBatchesDropped > 0`. На прогоне 08.09 было `batches_dropped=0`, `classify_batches_dropped=1`, `consolidated=29`, `unanswered=0` — то есть рендер-пас ЦЕЛ, а движок всё равно говорит «неполон». ⇒ **вывод «полон/неполон» из `dropped > 0` даёт ЛОЖНУЮ ТРЕВОГУ ровно на этом прогоне** — то, что пункт запрещает абзацем ниже. Полнота БАНКА выводится из рендер-паса; срез классификатора несётся ОТДЕЛЬНО. ⛔⛔ **ГЛАВНАЯ ЛОВУШКА ЭТОГО ПУНКТА, и наивная проекция в неё падает.** `exportBank` зовётся из ПЯТИ мест (`bookrun.go:180` run-start/seeded · `bookrun.go:258` run-finished · `mining.go:249` auto-continue · `mining.go:269` signature-stop · `status.go:1125` redrive/re-seeded), а `r.lastTerminology` заполняется РОВНО в одном (`mining.go:170`). Плюс `mining.go:66`: майнинг не сконфигурирован ⇒ терминология не запускается вовсе. ⇒ на четырёх границах из пяти счётчики нулевые, и «`consolidated=0 unanswered=0`» прочтётся как «неполноты нет» там, где её просто НЕ МЕРИЛИ. Это класс `D39.202` — прибор, отвечающий на свой вопрос, — и ровно он куплен строкой 312 из §4.3 («`toc_unresolved = 0`, потому что целей НОЛЬ»). **Заказ:** «не мерено» и «полно» обязаны быть РАЗЛИЧИМЫ на выходе; форма различения твоя. ⚠ Самый простой путь — граница: поля осмысленны там, где пас ШЁЛ (`signature-stop`, `run-finished`), и на `run-start/seeded` их быть не должно вовсе (`omitempty`), иначе «неполон» повиснет на проекции следующего прогона. ⚠ **Предъявление — не «поле добавлено», а «читатель видит»:** нужны ТРИ фикстуры, и третья — та, которую легче всего забыть: (1) банк неполон ⇒ артефакт и экран это НЕСУТ; (2) банк полон ⇒ молчат, ложной тревоги нет; (3) **терминология не работала вовсе** (граница run-start или майнинг не сконфигурирован) ⇒ выход говорит «не мерено», а НЕ «полно». ⚠ **КЛАСС A ЗАВОДИТСЯ СОЗНАТЕЛЬНО, и это надо ОБЪЯВИТЬ.** `18-bank-ontology.md` требует у проекции НАЗВАННОГО читателя. У новых полей его сегодня нет: `Consolidated`/`Unanswered`/`BatchesDropped` дают **0 хитов в `platform/internal/` при 183 go-файлах** (контроль: слово `bank` знают 65 из них). Читатель появится на платформенной стороне и заказан ПИНГОМ. ⇒ назови класс A в отчёте прямо, как это сделал предыдущий пак зоны (`D39.225` п.4), а не оставляй читателя подразумеваемым. ### 4.2 Два числа о деньгах, не сходящиеся с леджером — **делай РОВНО так** - **(а) Итог волны занижен.** `internal/pipeline/waverun.go:218` — `res.TotalUSD += r.lastTerminology.CostUSD`, а `CostUSD` по собственному докстрингу — «what THIS run's RENDER phase paid»; классификаторская стоимость лежит отдельным полем `ClassifyCostUSD` (`terminologist.go:145`, заполняется на `:406`) и в `TOTAL (this run)` не попадает вовсе. **Замер прогона 08.09 по строкам леджера:** терминологическая фаза стоила `$0.013608` = классификатор `$0.009013` + рендер-батчи `$0.002775` и `$0.001820`; в итог входит только рендерная сумма `$0.004595`, то есть **две трети денег фазы до итога не доезжают**. ⇒ **ВКЛЮЧИТЬ.** ⚠ Я рассматривала второй вариант («объявить в строке, что классификатор считается отдельно») и снимаю его: он оставляет `TOTAL` МЕНЬШЕ леджера, а этого пак запрещает ниже и требует §13. Вариант один. ⚠ Числа выше — из отчёта зоны о том прогоне (`docs/PROGRESS.md`, секция «Бэкенд»), СЫРОГО леджера того прогона в дереве НЕТ (замер: `.db` новее 07.09 — 0 при 163 в дереве) ⇒ предъявляй на СВОЕЙ фикстуре, а эти числа бери как описание симптома, не как мишень для воспроизведения. - **(б) Разложение относит классификатор в «купил ничего».** Обе банк-роли адресуются одним синтетическим именем стадии (`terminologist.go:65` `terminologyStageName`, проставляется обоим), а разложение ключуется `pos{Chapter, ChunkIdx, Stage}` без роли (`internal/pipeline/paidtail.go`), и более ранний из двух батчей с одинаковой позицией уезжает в `SupersededUSD`. Улика живого прогона — три строки леджера (`0 0 terminology classifier` · `0 0 terminology terminologist` · `0 1 terminology terminologist`) при печатном «the book's TERMINOLOGY … (2)», где два — последние. ⇒ развести позиции банк-ролей в ключе разложения. Роли различимы уже сегодня (`roleTerminologist` / `roleClassifier`), и колонка `role` в БД ЕСТЬ (`store/migrate.go`) — **миграция не нужна**. ⛔⛔ **ПРАВИТСЯ ЧИТАТЕЛЬ, НЕ ПИСАТЕЛЬ, и это денежная граница, а не вкус.** Соблазн дать классификатору СВОЁ имя стадии есть, и он покупает перепокупку: чекпойнты адресуются `EnsureJob(bookID, 0, terminologyStageName, snapID)`, новое имя стадии = новый джоб = старые чекпойнты не матчатся = **на резюме классификатор оплачивается заново**. ⇒ имя стадии НЕ переименовывать; роль доезжает до разложения отдельным полем. ⛔ **Но на ВХОДЕ разложения роли нет:** `store.CheckpointUsage` поля `Role` не несёт, и запрос её не выбирает (`internal/store/ledger.go`, тип и его `SELECT`). ⇒ **правка `internal/store/ledger.go` РАЗРЕШЕНА и входит в заказ** — это не выход за скоуп. ⛔ **И она делает ЛОЖНЫМ комментарий, стоящий в самом предмете:** `paidtail.go` про идентификацию класса по стадии — «equivalent to reading `checkpoints.role` and needing no store change». Правя механизм, снимай и это утверждение: держать два носителя одного знания в согласии — ровно то, что запрещает `D39.216` п.3(б). ⛔ **ТЕСТ, КОТОРЫЙ ОБЯЗАН ОСТАТЬСЯ ЗЕЛЁНЫМ И НЕ ПОДЛЕЖИТ ПРАВКЕ:** `TestAReboughtGlossaryBatchIsStillALoss` (`paidtail_test.go`) держит ДВА вызова в ОДНОЙ позиции и требует, чтобы ранний уехал в `superseded`. Это ДРУГОЙ случай — перепокупка ОДНОЙ роли при росте черновикового множества (строка 233), и она остаётся потерей. Твоя правка разводит РАЗНЫЕ роли, а не амнистирует перепокупку одной. Покраснел он — ты сломала класс, а не починила свой. ⚠ И существующие фикстуры `paidtail_test.go` — ровно вырожденный класс `D39.208` п.5: коллизионного случая (две РАЗНЫЕ роли в одной позиции) в них нет ни одного, поэтому батарея на этом дефекте зелёная. Фикстуру, где роли РАЗЛИЧНЫ, заводишь ты. ⛔ **Обе половины предъявляются СВЕРКОЙ С ЛЕДЖЕРОМ**, а не чтением кода: тест берёт числа отчёта и числа леджера на одной фикстуре и утверждает их отношение. ⚠ **Отношение назови точно, это два разных утверждения:** для (а) — РАВЕНСТВО (итог обязан сойтись с суммой строк), для (б) — что классификаторская строка попала в свой класс, а не в `superseded`. «Леджер есть нижняя граница» — про мир, а не про этот тест: там, где движок не записал вызов, равенства нет и быть не может, и это отдельный класс (`PD-441`, строка 331), в пак НЕ входящий. ⛔ **ГРАНИЦА ЭТОГО ПУНКТА, и она снята чтением ОБЕИХ сторон шва — не выводи её заново и не расширяй.** Это правка ОТЧЁТА, а не денег: платформа биллит НЕ `TotalUSD`, а `committed_usd` — сумму по таблице `spend` (`platform/internal/runs/reconcile.go` докстринг `attemptSpend`), а классификатор ходит тем же учтённым путём, что и все стадии (`runBankAttempt` → `runAttempt`, `terminologist.go:783-796`), и его деньги в `spend` УЖЕ лежат. ⇒ ни один счёт пользователя не двигается, согласия владельца пункт не требует. Если тебе кажется, что нужно тронуть ЛЕДЖЕР, — это стоп и пинг: там будет двойной счёт, а не починка. ⚠ **СОСЕДНЯЯ МИНА — не в этом пункте, но в шаге от него, и я называю её ГРАНИЦЕЙ, а не заказом.** Твоя правка `waverun.go` порога согласия НЕ касается: база порога — `projectBookUSD` в `internal/pipeline/rebill.go`, другая сумма. ⛔ Но если работа поведёт тебя в ПРОЕКЦИЮ (`ProjectedBookUSD`, `status`, добавка контура к `bookUSD`) — там порог СОГЛАСИЯ на пере-оплату считается БЕЗ банкового контура НАМЕРЕННО («THE CONSENT THRESHOLD IS COMPUTED WITHOUT THE CONTOUR, and the two numbers part company HERE»), и **первая версия той правки сложила контур ДО взятия порога и тихо ОСЛАБИЛА денежный гейт; поймала приёмка.** ⇒ пошла в проекцию — это стоп и пинг, а не «заодно». ### 4.3 Третий член того же класса — **реши сам, бери если по пути** Строка **312**, остаток Д-3: движок предупреждает о нечитаемом оглавлении в свой `Log.Warn`, а платформа читает `events.jsonl` — то есть та же форма «знает один, смотрит другой». Проверь сам, тот ли это класс; если да и если дёшево — закрой тем же движением. Если дорого или предмет другой — скажи в отчёте, оставлю строкой. ### 4.4 Чего в паке НЕТ — **не делай**, и это ОБЪЯВЛЕННЫЕ сужения - **Секция предложений сайдкара банка** — по ней применено правило остановки (`D39.225` п.10), гейт возврата назван строкой 353: появление первого ЧИТАТЕЛЯ секции. Четвёртый круг не открывается. - **Платформенные половины** строк 253 и 224 — чужая зона. Твоё действие — ПИНГ в отчёте, не правка. - **Всё, что двигает** снапшот, хеш запроса, ключ манифеста или версию встроенных данных. В этом паке заказанных смен поведения ДВЕ, и обе — в путях отчётности и артефактов: §4.1 и §4.2. - ⛔ **СТРОКА 360 — ТАЙМАУТ ЧТЕНИЯ, МАСКИРУЮЩИЙСЯ ПОД НЕЧИТАЕМОЕ ТЕЛО. НЕ БЕРЁШЬ, и я называю это прямо, потому что она ДОРОЖЕ всего, что в этом паке.** Замер полигона: 23.8–34.5 % цены редакторской руки; ошибка чтения выбрасывается в `_`, обрезок биллится по оценке и РЕТРАИТСЯ. Почему не здесь: (1) это другой класс — не «движок знает, а читатель не видит», а «движок не знает, что оборвал сам»; (2) её денежная половина требует слова владельца (платит ли пользователь за вызов, который оборвали МЫ) и сцеплена со строкой **331**, где тот же предмет закрыт ПРОТИВОПОЛОЖНО — нулём вместо оценки. Идёт отдельным паком. ⚠ Наткнёшься на неё по пути — **не чини, отдай наблюдением в отчёт**: молчаливая починка здесь смешает два денежных класса в одном лендинге. - **Строки 261 · 262 · 331** — члены того же класса «вызов оборвали мы»; уезжают вместе с 360. - **Переписывание `Runner`, дробление `internal/pipeline`** — запрещены `D39.224` §7. ⚠ «Новый детектор границ» там же — не запрет, а УСЛОВИЕ; к твоим задачам не относится, но силу пункта не путай. - **Платные вызовы** — ноль, см. §Деньги. ## 5. Мандат самопроверки ИСПОЛНЕНИЕМ — «перечитал сам» не считается 1. **Каждая новая гарантия предъявляется МУТАЦИЕЙ**, и краснота читается ПО ТЕКСТУ падения: говорит ли он про сломанное тобой. Правый вердикт по неправой причине — дыра, а не поимка. 2. **Мутации — ТОЛЬКО на копии дерева, и рецепт копии УЖЕ СТОИТ в цели `mutations` (`backend/Makefile`)** — своего однострочника не пиши, он станет вторым носителем и разойдётся. Гонишь батарейное подмножество — `make mutations`. Гонишь одиночную посадку `-id`, то есть копию делаешь руками, — повтори цель ДОСЛОВНО: распаковка в **подкаталог** `$tmp/backend` (а не в корень времянки), исключения `--exclude='./.env*'` и `--exclude=./bin`, и **два** трапа: `EXIT` убирает, а `INT TERM` убирает И ВЫХОДИТ (`exit 130`) — иначе прерванный прогон продолжает жить. Рабочее дерево не правится никогда, в том числе «на секунду». 3. **Мутируй и СВОИ новые фикстуры.** ⛔ И особая осторожность здесь: у тебя ДВА условных сообщения (неполный банк · пропущенный кандидат). Пин на каждое обязан иметь фикстуру, где оно ОБЯЗАНО прозвучать, и вторую, где оно обязано молчать. Предыдущий пак этой зоны выпустил ТРИ ложных операторских предупреждения подряд, и все три были зелёными именно потому, что утверждалось только молчание. 4. **Новые записи — в каталог** `cmd/tmmutate/mutations.json`. Ключи СТРОЧНЫЕ: `id`, `package`, `why`, `edits`, плюс необязательный `battery`. ⚠ `package` — пакет ПИНА, а не пакет правки: инструмент гоняет один пакет. ⚠ `battery` ставится не всем: сегодня это **48 записей из 259** — гейтовое подмножество; какие из твоих туда входят, решаешь ты и говоришь почему. 5. **BASELINE НА ВХОДЕ** (снят оркестратором 08.09): `make battery` → 19 `ok` · 0 `FAIL` · 4 «no test files» · 4 скипа поимённо; `make mutations` → **48 записей · 0 unexpected · `anchors swept: 0 of 259`**. Красное на входе — находка и пинг, а не твоя работа. 6. **Субагенты РАЗРЕШЕНЫ ЯВНО** — и на охват, и на адверсариальный проход по ГОТОВОЙ работе. Направление, что здесь уязвимее всего: (1) вторая половина утверждений про условные сообщения; (2) сверка отчёта с леджером — легко заявить и трудно доказать; (3) фикстуры, где «полон» и «неполон» случайно совпали. 7. **Проверяемый артефакт** — таблица мутаций в отчёте: `мутация → где посажена → красный тест → ТЕКСТ падения`, и отдельной строкой **выжившие**, если такие есть. ## 6. Оси ревью — характер «Код», плюс деньги (ось вправе заменить с аргументом) Тест утверждает не то, что думает (`D39.171`): часы как способ синхронизации там, где предмет не время, и ассерт по подстроке в буфере, куда пишет кто-то ещё. Обе формы дают и ложное красное, и **тихо-зелёное** — оно дороже: зелёное, не проверившее ничего, не перечитывает никто. Третья ось твоя: **деньги** — любое число выводится из СЫРОГО леджера, и леджер есть нижняя граница, а не оценка. ## 7. Записка-план ДО работы До первой правки — короткая записка в отчёт: что берёшь, в каком порядке, что считаешь рискованным. После работы — сверка комплектности против §4 МЕХАНИЧЕСКИ (таблицей), не перечитыванием. ## 8. Заявление = команда Каждое число и категорика отчёта — с командой, которой получены. Отрицательный замер печатается ВМЕСТЕ с контрольной величиной, доказывающей, что вопрос задан существующему предмету. Сверх этого, про код: - **перед отчётом сверь каждый клейм с результатом инструмента ЭТОЙ сессии**, а не с памятью и не с этим промтом: адреса §4 сняты 08.09 и могли уехать; - **дифф `^func Test` — исполнением**, ИМЕНАМИ через `comm`, а не разностью счётчиков; - **последний абзац отчёта оказался планом или обещанием? сделай это СЕЙЧАС**; - ⛔ править или удалять тест/голден/гейт, чтобы он прошёл, — НЕДОПУСТИМО; исключение — правка от заказанной смены поведения (§4.1, §4.2), и она объявляется отдельным пунктом; - сессия длинная — поставь себе интервальную самоверификацию субагентом против ЯВНЫХ критериев §13. ## 9. Эхо-протокол старта ДО работы — ≤10 строк **СВОИМИ словами**: скоуп · инварианты, которые не имеешь права сломать · что НЕ делаешь. Дословный пересказ промта подтверждает канал, но не понимание, и ошибку промта повторяет вместе с ним. Отправь эхо оркестратору по адресу из прямого канала (§12); канала нет — эхо идёт первой секцией отчёта, и работа продолжается. ## 10. Что НЕ удалось — обязательная секция отчёта Что не вышло, что не проверено, где сомневаешься. Пустая секция при непустой работе — сама по себе находка; «не проверено» и «опровергнуто» — РАЗНЫЕ исходы. ## 11. Канал вопросов и право сказать «этого делать не надо» Конфликт промта с кодом или доками — **пинг, а не интерпретация**: промт мог устареть, и в этом проекте это случается — за прошлые сутки в промтах и трекере нашли шесть ложных утверждений, три из них авторства оркестратора. Считаешь пункт ненужным — скажи с доводом; такие возражения ценнее послушного исполнения. ## 12. Прямой канал Механизм — `CLAUDE.md` §«Связь между сессиями». Впиши СВОЙ блок в `/tmp/textmachine-channel` первым делом, чужие блоки не трогай. ⚠ Адрес из файла не доказывает, что сессия жива: сверься с `ListAgents`. Нужной роли нет или имя не отвечает ⇒ **канала нет, и это НОРМА: не опрашивай сессии подряд** — вопрос секцией в отчёт. ## 13. Критерий завершённости — по нему тебя примут Работу доводишь до конца САМ; приёмка идёт последней и вторым кругом твоей самопроверки не служит. Готово — это когда: - у КАЖДОГО пункта §4 есть исход: сделано · не делаю с доводом · пинг; - круги СОШЛИСЬ: последний проход не дал НОВЫХ находок, прежние закрыты таблицей «находка → что сделано → ЧЕМ ПРЕДЪЯВЛЕНО»; - **каждый класс закрыт с ЗНАМЕНАТЕЛЕМ: «закрыт в N носителях из M»**, и M посчитан командой, а не на глаз; - таблица мутаций полная, выжившие названы; - **числа отчёта сверены с СЫРЫМ леджером**, а не с логом; - числа сняты ПОСЛЕ последней правки, а не вспомнены; - всё живое — в ДЕРЕВЕ, а не в письме; - сказано явно: **«работа завершена, править не планирую»**. Без этой фразы пак считается идущим. ## Деньги **Пак $0.** Платных вызовов ноль: всё, что нужно предъявить, предъявляется на ФЕЙК-провайдере и на собственных фикстурах. ⚠ **Сырого леджера прогона 08.09 в дереве НЕТ** (замер: файлов `.db` новее 07.09 — **0** при 163 в дереве, свежайший от 02.09) — числа этого прогона живут только описанием в отчёте зоны, и воспроизводить их не надо. Если тебе кажется, что задача требует платного прогона, — это стоп и пинг, а не вызов. ## Отчёт Секция **«Бэкенд»** в `docs/PROGRESS.md`, подзаголовок `#### Пак «движок уже знает»`: исход по каждому пункту §4 · таблица мутаций · таблица «находка → что сделано → чем предъявлено» · столбец «закрыт в N из M» · сверка отчёта с леджером числами · пинги в чужие зоны (платформенные половины 253 и 224) · §10 «что не удалось» · вопросы оркестратору.