# ДОФИКС бэкенд-паку «число согласия на пути второй покупки» — по приёмке оркестратора №22 (03.09.2026) > **Роль:** бэкенд, та же сессия, что отработала пак. **Зона записи:** `backend/` + своя секция > «Бэкенд» в `docs/PROGRESS.md`. Чужие зоны читаешь, не правишь. **Не коммитишь** и не трогаешь индекс > и историю — запреты и причина: `CLAUDE.md`, «Git-координация мультисессий». > ⛔ **В индексе сейчас лежит НЕЗАКОММИЧЕННАЯ работа двух смен (46 файлов).** Ни одной пишущей > git-команды: ни `checkout`, ни `restore`, ни `stash`, ни `reset`, ни `clean`, ни `add`, ни `commit`. ## Что это и почему оно пришло Пак **принят с дофиксом**. Приёмка шла не чтением отчёта: десять линз (пять на зону), каждая обязана была проверять исполнением, каждая находка ушла опровергателю с мандатом её ОПРОВЕРГНУТЬ своей командой. По бэкенду 20 находок, 19 пережили опровержение. ⚠ **Твой код в основном подтверждён — ниже отдельная секция «что приёмка проверила и признала», и её переделывать не нужно.** Дофикс про другое: пак создал один дефект в том, что сам чинил, оставил ложным одно РАТИФИЦИРОВАННОЕ утверждение, и три свойства, объявленные твоими, тестом не прибиты — мутации приёмки их пережили. ## Карта чтения — три позиции 1. Этот файл. Твой прежний промт `docs/BACKEND_CONSENT_SESSION_PROMPT.md` — уже прочитан, перечитывать целиком не надо. 2. Свой отчёт в `docs/PROGRESS.md`, секция «Бэкенд», запись 03.09 — по пунктам Б7–Б10 правится он сам. 3. Улики ниже. **Каждая проверена приёмкой исполнением; воспроизведи их сам** — если что-то не воспроизводится, это находка, неси её, а не подгоняй работу под текст. ## Б1 [важное] — стоп-строка и лог лгут ровно в том случае, который пак СОЗДАЛ `bound()` теперь истинен при `Carried > 0`. Значит прогон, дошедший **до конца книги** и лишь дописавший начатое, печатает оператору самопротиворечие: ``` backend/internal/pipeline/volume.go:220 — «stopped on the VOLUME ceiling (--max-units %d), not on money and not at the end of the book» — префикс БЕЗУСЛОВЕН, внутри самого fmt.Sprintf и тут же в том же предложении: «Still in the book: 0 unit(s) NEVER delivered» ``` Воспроизведено живым прогоном настоящего `Runner` (две главы, первый прогон умирает на редакторе, второй с `MaxUnits=2`): `Left()=0 granted()=0 Paid()=2`, и обе половины предложения печатаются. Тот же текст едет в лог `waverun.go:173`, а комментарий у вызова `waverun.go:169-170` («Only set when the ceiling actually held work back») протух — **в файле, который пак правил**. **Делай РОВНО так:** предложение разводится на два случая — грант кончился / книга кончилась. **Решаешь сам:** формулировки обеих веток и нужен ли отдельный текст для смешанного случая. ⚠ Пункт 2 пака закрывался ИМЕННО как дисклоужер — объявлять его закрытым до этой правки нельзя. ## Б2 [важное] — ратифицированный контракт кадра `finished.volume` стал ЛОЖНЫМ `D39.181 п.2` объявляет ПРИСУТСТВИЕ поля булевым признаком «грант придержал работу»: ``` backend/internal/runevents/runevents.go:189 «present only when a volume grant actually held work back» backend/internal/runevents/runevents.go:202-204 «its PRESENCE is the boolean: the engine attaches this only when the grant actually held something back» те же утверждения — backend/internal/pipeline/events.go:584-588 и bookrun.go:91 ``` Кадр теперь приезжает и у прогона, дошедшего до конца книги (`left_fresh:0`, `left_rework:0`), и `carried` на провод не выходит вовсе — то есть читатель различить два состояния НЕ МОЖЕТ ничем. Пак переписал соседние поля ТОЙ ЖЕ структуры (`Delivered`, `Free`), а утверждение тремя строками выше оставил. Сегодня безопасно только потому, что потребителя нет: `platform/internal/ingest/events.go` декодирует `Finished{Outcome}` и всё. **Делай РОВНО так:** текст контракта приводится в соответствие с поведением ВО ВСЕХ ЧЕТЫРЁХ местах. **Решаешь сам:** формулировка. ⛔ **Поля на провод НЕ добавлять** — это класс «новое поле в опубликованном документе», он требует ратификации и минора `StreamVersion`, и владелец 03.09 уже сказал «оставить как есть». Если считаешь, что честный контракт без поля невозможен, — **пинг**, а не поле. ## Б3 [важное] — три свойства объявлены твоими и НЕ прибиты: мутации приёмки пережили весь пакет Все три посажены приёмкой на копии дерева, каждая SURVIVED на `./internal/pipeline/`, и в том же прогоне соседние посадки дали RED — то есть положительный контроль стоит. - **(а) `volume.go:191`** — `case class == unitFresh && s.stop.Delivered > s.stop.Carried:`. Возврат к форме `> 0` (и эквивалентной `>= 1`) переживает пакет. Существующий `TestReconcileKeepsCarriedInsideDelivered` (`volumeslot_test.go:554`) гоняет только ветки `unitFree` и до этой ветки не доходит. ⚠ Отчёт числит за этой находкой «посадку **Y3**», **которой в репозитории нет** — см. Б7. - **(б) `rebill.go:450`** — подмена `projectionBasis(proj)` на `projectionBasis(book)` переживает пакет. Ветка-близнец (scoped-клауза, `:505`) от той же подмены КРАСНЕЕТ через `TestTheScopedClauseOfTheRefusalCarriesItsOwnBasis` — значит пакет эту подмену ловить умеет. - **(в) `rebill.go:323`** — `if p.ModelMovedRows > 0` заменяется на `>= 0`, и «0 of the N unit(s) were bought from a model…» уезжает в КАЖДЫЙ текст согласия; пакет зелёный. У близнеца `HistoricalRows` отрицательный пин ЕСТЬ (`reprice_test.go:569-572` проверяет, что чистая сумма НЕ содержит «originally billed»), у новой оговорки — нет нигде. **Делай РОВНО так:** каждое свойство получает тест, который от своей мутации краснеет, И запись в `backend/cmd/tmmutate/mutations.json`. **Решаешь сам:** форма тестов и формулировки записей. ⚠ Приёмка сажала СВОИ формы мутаций — не копируй их, посади свою и убедись, что она красная. ## Б4 [мелочь] — докстринг `classifyUnits` не пере-считан под собственную правку `volume.go:637`: «sorts every output unit into the **three** things this run can do to it», а `unitClass` несёт ЧЕТЫРЕ члена, и добавленный паком `unitCarried` присваивается ВНУТРИ самой этой функции. Вредно адресно: читатель, ищущий, куда попадает начатый-но-не-отгруженный юнит, по слову «three» решит, что четвёртого класса нет, — и пойдёт искать в ветку `unitFresh`, ровно туда, где жил дефект строки 232. **Делай РОВНО так**, правка на одно слово. ## Б5 [мелочь] — пример в `backend/README.md:73` противоречит новой семантике Строка обещает «оплатить не больше 10 выходных юнитов», тогда как тело README:11 новую семантику раскрывает верно. **Делай РОВНО так:** привести пример к телу. ## Б6 [мелочь, по желанию] — шов `store → репрайсер` прибит с одной стороны Подмена `c.escalation` на `0` в `ledger.go:380` переживает `./internal/store/`, а `./internal/pipeline/` краснеет через `TestAnEscalatedRowIsRePricedAtTheNewHopEndToEnd`. Дефектом сегодня не является — пин живёт там, где живёт свойство. Но твой же довод по фильтру книги («мутация пережила обе батареи по отдельности, значит одной мало» — оттого FC1-book-filter-**store** И -**pipeline**) применим и здесь. **Решаешь сам:** заводить ли запись каталога на store-стороне; отказ аргументируй. ## Б7 [среднее] — отчёт ссылается на ПЯТЬ несуществующих посадок как на живые гейты `Y2-silent-when-nothing-left` · `Y3` · `Z2` · `X29` · `Y4`. Каталог получил ровно ШЕСТЬ записей (`FC1-*`×4, `FC5-*`×2); 35 заявленных собственных посадок жили в scratch-каталогах (`own_mutations.json`, `round2_mut.json`, `round3_mut.json`, `draftonly_mut.json`), которых на машине нет. Проверено грепом с границами слова, положительный контроль на ту же форму: `FC1-book-filter-store` находится в двух местах. Читатель отчёта принимает пять несуществующих гейтов за живые — а один из них «закрывает» гарантию, чья мутация переживает (Б3а). **Это ровно та болезнь, ради которой `tmmutate` и лежит в репозитории.** **Делай РОВНО так:** либо снять ссылки, либо посадить недостающие записи в каталог. **Решаешь сам:** какие из пяти стоят записи, а какие были разведкой. ## Б8 [мелочь] — четыре числа отчёта не печатаются командами, которые он для них называет - «денежного пути в каталоге было 0 файлов, стало **5**» — каталог даёт 18 → 21 различных файла, то есть ТРИ новых (`reprice.go`, `volume.go`, `ledger.go`), шесть записей; греп из промта печатает 7. Ни одно прочтение не даёт 5. - «на входе **3** попадания» у грепа `can never quote a different number` — на входном коммите в названной области попадание ОДНО. - «все **18** строк» линта — гейт печатает **19**, и девятнадцатый создан самим отчётом (строка, цитировавшая мёртвый якорь в самом отчёте). - «в файле **485** строк» — 499: число снято до того, как отчёт дорос. **Делай РОВНО так:** пере-считать каждое своей командой и вписать команду рядом с числом. ⚠ `python3 docs/scripts/counts.py --lint` гнать **ПОСЛЕДНИМ действием**, после того как отчёт дописан, и починить 19-й якорь — иначе гейт остаётся красным по вине самого отчёта. ## Б9 [мелочь] — неполнота самообъявления по D39.183 `TestCheckpointsOfOneGenerationSumToTheRowsCost` (`reprice_test.go:639`) адаптирован под третий возврат `usd()`, а в блоке самообъявления не назван, хотя остальные четыре адаптации под ту же смену сигнатур перечислены поимённо. Ослабления нет — правка механическая. Это ровно класс, который бэклог держит строкой **248**. **Делай РОВНО так:** дописать имя. ## Б10 [к сведению, НЕ заказ] — что приёмка нашла и что сознательно НЕ чинится этим дофиксом Назови это в отчёте, чтобы оно не умерло молча вместе с паком. - **`Carried` на провод не выходит**, а подсказка, дописанная паком в докстринг `Delivered`, ключуется на `delivered > max_units` — и в форме «весь остаток перенесён» `delivered == max_units`, то есть подсказка НЕ срабатывает и разложить кадр читателю нечем. Связано с открытым вопросом владельца о семантике переноса; чинится вместе с ним, а не отдельно. - **В перенос попадает и юнит, который ФЛАГНУЛСЯ и текста не отдал** — он «начат и не отгружен», значит каждая следующая покупка снова платит за его пере-атаку ВНЕ гранта. Приёмка это вывела из `classifyUnits` и подтвердила зелёными тестами пака, но **цикла из трёх последовательных покупок не гонял никто**. Это вход в решение владельца, а не дефект — не чини, но не потеряй. ## Границы — что НЕ делать - ⛔ **Не добавлять поле на провод** (см. Б2) и не двигать `StreamVersion`, форму манифеста, схему хранилища, версию контракта. - ⛔ **Не менять семантику переноса слота.** Владелец 03.09 отложил её словом «подумаем на этот счёт». В коде сегодня «перенос ограничен грантом», и приёмка это ЗАМЕРИЛА: грант 2 оплачивает 4 выходных юнита, 6 вызовов провайдера. Это открытый вопрос владельца, а не дефект — трогать нельзя. - ⛔ **Не править `docs/architecture/15-money-path.md` и тело `D39.170`** — они противоречат новой семантике `--max-units`, это правда, но это зона оркестратора, и он их правит сам. Ты правильно сделал, что не полез. - ⛔ Не трогать чужие зоны, `.env`, соседний worktree. ## Что приёмка ПРОВЕРИЛА и признала — переделывать не нужно - Батарея зелёная: `EXIT=0`, `0 issues`, три известных SKIP, ни одного FAIL. Тестов **1083 → 1102**, удалённых имён **НОЛЬ** (пофамильный дифф). - Все ШЕСТЬ каталожных мутаций пере-посажены и **все шесть КРАСНЫЕ**. - Четыре обязательства `D39.150 п.1` исполнены ВСЕ, включая четвёртое: обещание точности снято из `status.go`, шапка `reprice.go` заявляет обратное прежнему. - Инвариант направления ошибки держится по построению: в `repriceCheckpoint` цена только повышается — пол по ответившей модели, подъём до сегодняшней. - Правки существующих тестов проверены на МОТИВ: подгонки нет, все стали СИЛЬНЕЕ (проверка трёх полей → проверка всей структуры; добавленное утверждение про basis). - Границы соблюдены: схема, манифест, контракт не двинуты; `runevents/` тронут только комментариями; два новых поля `status --json` аддитивны — платформа декодирует аллоулист. - Кратность оценки `$0`-строки названа честно комментарием и ратифицирована «round up when it cannot be known» — приёмка это принимает. ## Мандат самопроверки — исполнением 1. **Каждый пункт Б1–Б9 проверяется своей командой**, и в отчёте стоит её выход цитатой, а не пересказ. 2. **Мутации Б3 сажаются на копии дерева** (`cp -a backend /tmp/…`) и предъявляются КРАСНЫМИ. Посадка, не покрасневшая, — находка, а не провал: так и пиши. 3. **Отдельный проход по своей готовой работе** свежим контекстом. Субагенты разрешены явно, модель задавай ИМЕНЕМ и знай, сколько их работает. 4. ⚠ **«Прошёл, находок нет» отчётом НЕ является**: ноль находок обосновывается тем, ЧТО искалось и какими командами. ## Отчёт В свою секцию `docs/PROGRESS.md`. Пройди **по каждому** пункту Б1–Б9 и отчитайся по каждому — эта тема в смене проваливалась несколько раз на том, что исполнялся первый пункт, а нумерованный остаток бросался. Каждое число — с командой получения, которая его печатает; приёмка пере-ранит. Обязательная секция **«Что НЕ удалось и что НЕ проверено»** — третий исход законен. Конфликт этого наряда с кодом — **пинг**, не интерпретация: наряд писан по дереву индекса, код первичен. ## Прямой канал Механизм — `CLAUDE.md`, «Связь между сессиями»: свой блок в `/tmp/textmachine-channel`, сверка с `ListAgents` перед отправкой; канала нет — нормальный случай, сессии подряд не опрашивать.