> ⚠ **ОТРАБОТАН И ЗАКРЫТ 29.08 — D39.170.** Пак принят и заленджен. Исход: потолок ОБЪЁМА оплаченной > работы построен; читающий путь `status` сворачивает банк, поэтому смета пере-прохода впервые доезжает > до покупателя ДО покупки, оставаясь $0-глаголом. Приёмка шла ЧЕТЫРЕ круга: первые три сдачи были > неверны, и блокирующую находку — потолок пробивался пере-сидом банка посреди прогона — дал охотник вне > карты, а не отчёт. Отложены строками **232** (ось отгрузки на `once_key`) и **233** (терминолог вне > потолка); проводка `--max-units` в платформу ГЕЙЧЕНА. **Инструкции отсюда НЕ исполнять** — файл > историчен. # Промт: бэкенд, пак «деньги» — продажа обретает настоящий стоп, а смета становится доступной до покупки > **Выдан оркестратором №19, 28.08.2026.** Основание — **D39.165** §1 (три продуктовых развилки сняты > словом владельца) и строка бэклога **231**. Пак идёт ПАРАЛЛЕЛЬНО платформенному. > ⚠ **ФАЙЛЫ вы не делите** — модули отдельные, платформа кода движка НЕ компилирует и его тестов НЕ > запускает (проверено). **Но ШОВ общий и ведётся РУКАМИ в трёх точках:** таблица кодов выхода · > argv из строковых литералов · аллоулист полей `status --json`. **Твой §3.1 трогает первые две** — > форму ответа я решил за тебя (§3.1), а не оставил на согласование. ## §0. Какая проблема и что решит твой результат **1. Ручка «купить N глав» ничего не ограничивает.** Платформа продаёт ГЛАВЫ, а движку передаёт только денежный потолок: `--ceiling-usd` (`backend/cmd/tmctl/invocation.go:129`), и тот каппит **КУМУЛЯТИВНУЮ** трату по книге (`backend/internal/store/ledger.go:67` — `bookTotal+estimate > c.BookUSD`). **Про число глав движок не знает вовсе**, флага такого нет. **Измерено приёмкой 28.08 (леджеры реальных прогонов, $0):** настоящая цена главы — средняя **$0.0115–0.0190**, максимум одной главы **$0.0375**; константа продажи платформы — **$0.03**. То есть покупка «10 глав» отдаёт движку $0.30, а $0.30 на этом материале покупает **16–26 глав**. Пользователь просит одно, получает другое, и полоса каппится на купленном. ⚠ **И хвост цены порождает КАЧЕСТВО, а не объём** (наблюдение владельца, подтверждено данными): вызов может отработать успешно и вернуть мусор — деньги списаны, юнит уезжает на ретрай, иногда на эскалационный хоп к дорогой модели. В леджере это видно с именами причин (`empty`, `length`, `cjk_artifact`, `sanitizer_defect`), и доля ретраев+эскалаций гуляет **1.5% → 23%** между прогонами. **2. Смету пере-прохода показать НЕЧЕМ — и это названная цена вчерашнего минора 0.7.0.** Движок уже считает её (`projectRebill`, `backend/internal/pipeline/rebill.go:111`) и публикует в `status --json`. Но `status` материализует банк из СОХРАНЁННОГО глоссария (`backend/internal/pipeline/status.go:733-744`, `projectStoredMemory` — его собственный комментарий: «A seed-FILE edit not yet re-run is NOT reflected here»), а `bank-apply` пишет только ФАЙЛЫ решений. Свёртка происходит внутри СЛЕДУЮЩЕГО `translate`. **Итог: сразу после правки банка движок честно отвечает «ничего не двигалось», и платформе нечего показать покупателю.** Носитель — строка **231**. **Что решит результат:** движок получает СПОСОБНОСТЬ остановиться по объёму, а пользователь получает возможность увидеть, что затронет пере-проход, ДО оплаты. ⛔ **ЧЕСТНАЯ ГРАНИЦА, которую первая редакция промта скрывала (нашёл опровергатель).** «Купил N глав — получил N глав» этим паком НЕ становится правдой, потому что **число глав до движка не доезжает**: `CeilingChapters` живёт на платформе (`platform/internal/runs/runs.go:232`) до самого спавна, а `runner.TranslateArgs` собирает argv литералами и канала для него не имеет. **Проводка — платформенная половина, и она в этом паке НЕ заказана.** Ты строишь механизм; труба к нему — отдельный заказ, и я его завожу строкой. Не считай пак провалившимся оттого, что после него обещание §0 ещё не выполнено. ## §1. Зона записи и git **Твоя зона — `backend/`.** Итоги и находки — своя секция журнала `docs/PROGRESS.md` («Бэкенд»). - **Ты НЕ коммитишь.** Лендит оркестратор после адверсариальной приёмки. - ⚠ **`platform/` НЕ ТРОГАТЬ.** Там параллельно работает платформенный пак. Нужна правка на той стороне — это ПИНГ, а не правка. - ⚠ **`docs/` — не твоя зона**, кроме своей секции журнала. ⚠ Перед записью в неё **перечитай файл**: журнал правят две сессии, и 28.08 оркестратор уже унёс чужую секцию своим коммитом из-за общего окна записи. - ⚠ **Стендовый `tmctl` собирай из ЗАФИКСИРОВАННОЙ копии дерева, а не из рабочего**, если он тебе понадобится: рабочее дерево грязное твоими же правками, и бинарь из него мерит непонятно что. - В дереве незакоммиченная работа полигона (20 позиций) — не касаться. ## §2. Карта чтения — ≤5 позиций, ЗАКОН 1. **`backend/cmd/tmctl/main.go:252-262`** — три денежных флага и почему они ОРТОГОНАЛЬНЫ; там же предупреждение «`--ceiling-usd` — НЕ бюджет прогона» · **`invocation.go:120-210`** (парсинг и валидация флагов). 2. **`backend/internal/store/ledger.go:30-80`** — где потолок реально судит трату (`Reserve`). 3. **`backend/internal/pipeline/rebill.go`** — `RebillProjection`, `projectRebill` (`:111`), порог согласия и его текст отказа (`:318-324`). 4. **`backend/internal/pipeline/status.go:733-744`** — `projectStoredMemory`; **читать ВНИМАТЕЛЬНО: это и есть слепое окно из §0.2**. 5. **`docs/architecture/05-decisions-log.md`, тело D39.165** (греп `^## D39.165`) — основание пака; **и эррата 28.08-к в шапке того же файла** — там названа моя ошибка про смету, не повтори её. ## §3. Состав пака ### §3.1. Потолок ОБЪЁМА рядом с денежным — ЗАКАЗ; форма свободна **Заказано:** движок умеет остановиться по объёму работы, а не только по деньгам, и **говорит, по какому потолку встал**. **Ратифицировано D39.165 §1б и обсуждению не подлежит:** потолок живёт в ДВИЖКЕ. Резать волну на стороне платформы нельзя — она волной не владеет и пер-юнитной цены заранее не знает. **Свободен и обязан обосновать — В ЧЁМ мерить. ⚠ Кандидатов ТРИ, а не два (поправка опровергателя):** (а) **выходной юнит** — `ManifestUnit`, та самая гранулярность, которую движок отгружает, и она же платформенный `chapters.units_total` с интейка; (б) **чанк**; (в) **`chunk×stage` — БИЛЛИНГОВАЯ единица движка**, ровно та, в которой считает смета (`rebill.go:322`: «%d chunk×stage unit(s)»). ⚠ **Ловушка ровно здесь:** режущий волну естественно считает то, за что ПЛАТЯТ, то есть (в), а платформа продаёт (а) — и купивший десять глав получит их долю. **Дефект пака воспроизведётся слоем ниже, в той же форме.** Выбор объясни и назови, как он ложится на платформенную единицу продажи. ⚠ **И ещё одно, чего первая редакция не спрашивала: как против объёмного потолка считаются РЕПИНЫ и РЕТРАИ.** Пере-проход, пере-привязывающий пятьсот юнитов за $0 (`rebill.go:315-320`), сожжёт объёмный потолок, не потратив цента. Ответь на это явно. ⛔ **ФОРМА ОТВЕТА ЧЕРЕЗ ШОВ РЕШЕНА МНОЙ, и это НЕ предмет твоего выбора — потому что выбор здесь ломает деньги в обе стороны (нашёл опровергатель).** Словарь кодов выхода объявлен ЗАМОРОЖЕННЫМ самим движком (`backend/cmd/tmctl/main.go:125-127`: «both bands are frozen seams and a fresh code would be a word added to a ratified dictionary»), а платформа незнакомый код читает как ОТКАЗ (`platform/internal/ingest/exit.go:168-169`, `default: OutcomeFailed`). Отсюда: - переиспользуешь код денежного потолка → платформа объявит прогон ПРИОСТАНОВЛЕННЫМ и возобновит его за уже купленный объём — дефект, ради которого пак заведён, воспроизведётся слоем ниже; - заведёшь новый код → пользователь, получивший РОВНО купленное, увидит «ошибка сервиса». **РЕШЕНИЕ: остановка по объёму — это НЕ остановка, а ЗАВЕРШЕНИЕ.** По деньгам прогон встал ПОСРЕДИ работы, там честно «приостановлен» и возобновление. По объёму он сделал ровно то, что куплено, — это УСПЕХ, код **0**, замороженный словарь не трогается вовсе, платформа читает штатное «готово». Различение, которого требует пункт ниже, живёт тогда не в КОДЕ ВЫХОДА, а в отчёте/логе. Если найдёшь, что это ломает что-то, чего я не вижу, — **пинг, и я пере-решаю**: на этой развилке уже стоял выбор, ломающий деньги в обе стороны. ⚠ **Две грабли, забранные у внешних систем — исполнить обе:** - **остановка обязана НАЗЫВАТЬ свой потолок.** У OpenHands (`max_budget_per_task` + `max_iterations`) агент упирается в лимит итераций МОЛЧА — известная жалоба. У нас «встал по деньгам» и «встал по объёму» — разные ответы пользователю и разные ремеди; - **проверять ПЕРЕД началом единицы работы, а не после.** У LiteLLM пост-фактум-проверка токенного потолка всегда пробивает его на один вызов. Денежный потолок у нас проверяется в `Reserve` до вызова — объёмный обязан вести себя так же. ⚠ **Не сломай ортогональность.** `--ceiling-usd` — кумулятивный КНИЖНЫЙ кап, не бюджет прогона (`main.go:256-260`). Новый потолок — про работу ЭТОГО прогона. Смешаешь семантику — сломаешь платформенный расчёт холдов, который на кумулятивности и стоит. ### §3.2. Смета БЕЗ покупки — ЗАКАЗ; форма свободна **Заказано:** платформа может узнать «сколько юнитов затронет пере-проход», НЕ запуская `translate` и ничего не покупая. **Что знать** (проверено мной, пере-проверь): смета уже считается (`projectRebill`) и уже публикуется в `status --json` полями `rebill_units`/`rebill_usd`. Мешает ровно одно — **тайминг свёртки**: `status` читает СОХРАНЁННЫЙ глоссарий, а решения лежат в ФАЙЛАХ до следующего `translate`. **Форма свободна.** Очевидные кандидаты, оба со своей ценой: - **флаг у `status`** («сверни решения в память для расчёта и не пиши») — дёшево, но `status` ратифицирован как ЧИСТО ЧИТАЮЩИЙ ремонтный глагол, и свёртка внутри него это свойство ломает; - **новый $0-глагол** — честнее по разделению, но новая поверхность CLI и новый документ. ⚠ **Родня, которую надо знать: `resnapshot --dry-run`** — строка **124(в)**, поименована в законе шва как будущая дверь. Она просит ТУ ЖЕ способность «оценить, ничего не покупая». **Если твоя форма закрывает обе — скажи это прямо, это удешевляет проект.** ⛔ **Границы, обе жёсткие, но сформулированы ТОЧНО (первая редакция обе переоценила — нашёл опровергатель):** - **$0 — абсолютно:** ни одного провайдерского вызова. - **Read-only — с ОДНОЙ названной оговоркой:** «ни одной записи в стор» абсолютом быть НЕ МОЖЕТ — первое касание проекта создаёт и мигрирует базу движка по построению (`backend/internal/pipeline/runner.go:193-197`; ратифицировано D39.122, у `status` тот же побочный эффект). Требование верное: **никаких записей СВЕРХ этого first-touch**, и ни одной записи в банк/глоссарий/чекпойнты. - **Ключей НЕ требует.** ⚠ Якорь первой редакции (`dotenv.go:53-55`) я ВЫДУМАЛ — там середина докблока про другое. Настоящий носитель: `backend/cmd/tmctl/invocation.go:152-155` (текст отказа, перечисляющий читающие глаголы) и пин `backend/cmd/tmctl/keysfile_test.go`. ⚠ **ТРЕТИЙ КАНДИДАТ, ДЕШЕВЛЕ ОБОИХ МОИХ — его нашёл опровергатель, и я его не видел.** У `bank-apply` УЖЕ ЕСТЬ режим проекции (`invocation.go:126`: «print the projection of the decisions and write NOTHING»), он берёт тот же арбитр-флок, ничего не пишет и печатает JSON-отчёт. **И у него уже на руках ровно недостающее состояние**: `readBookState` поднимает bank + seed + delta + rejects ДО свёртки. Не хватает только statuses и manifest для `projectRebill`. Рассмотри этот путь ПЕРВЫМ: он не трогает «`status` — чисто читающий» вовсе. ⛔ **ЛОВУШКА, КОТОРАЯ УБЬЁТ ВЕСЬ СМЫСЛ, если её не увидеть.** Свернуть решения В СТОР ты не сможешь (`store.go:117-128`, `OpenReadOnly` ставит `PRAGMA query_only=1`), значит фолд будет В ПАМЯТИ. Но канонический фолд идёт ЧЕРЕЗ стор: `seeding.go` пишет банк, читает его обратно `ORDER BY src, sense, …` и материализует. Порядок НЕ безразличен — `membank/memory.go:610-617` сортирует стабильно «в порядке замороженных entries», а дальше токенный бюджет РЕЖЕТ хвост. **Материализуешь «будущий банк» в порядке добавления — получишь другой набор строк глоссария, другие отрендеренные байты, другой content-hash: бесплатная смета назовёт одно число, а платный прогон выставит другое.** Это ровно тот дефект, который пак продаёт как решённый. Докажи тождество, а не предположи его. ⚠ **И мина формы:** оба поля сметы объявлены `omitempty` (`status.go:208-209`). D39.166 п.2 уже заплатил за это однажды: «$0-цена мурует дверь — юниты по нулевой цене приходят БЕЗ цены». Если переиспользуешь форму, «ноль юнитов» и «не считалось» на проводе станут неотличимы. ⚠ **Факт из строки 231, который тебе полезен:** поля `rebill_units`/`rebill_usd` СНЯТЫ с аллоулиста шва до появления потребителя — вернутся вместе с твоим глаголом. ### §3.3. Чего в паке НЕТ — пропуски подписаны - **Калибровка константы $0.03 — НЕ твоя и НЕ сейчас.** Она платформенная и гейчена строкой **202** (живой прогон текущего стека). Ты даёшь МЕХАНИЗМ, цену считает не движок. - **Цена от объёма исходника** (D39.165 §1а) — платформенная половина, носитель `chapters.units_total`. - **Строки 228 / 230 / 141-остаток / 131** — соседи, не заказ. Увидишь — назови строкой, не чини. ## §4. Самопроверка ИСПОЛНЕНИЕМ — «перечитал сам» её не удовлетворяет 1. **Батарея зоны** под `-race`, линтер 0 issues; числа — с командами. 2. **Потолок объёма доказывается ПОСТ-ФИКСНЫМ инвариантом:** прогон с потолком в N единиц останавливается на N, отвечает признаком «встал по ОБЪЁМУ» (отличимым от денежного), и останавливается ПЕРЕД началом N+1, а не после. 3. ⛔ **Смета доказывается $0 — и РЕПРО ПЕРВОЙ РЕДАКЦИИ БЫЛО ВЫРОЖДЕННЫМ, я повторил ошибку, о которой сам же написал эрратy 28.08-и.** Состояние «`translate` не запускался» негодно: у такой книги строк `chunk_status` нет вовсе, и `projectRebill` отдаёт ПУСТО и до фикса, и после (`rebill.go:132-134` пропускает строки без снапшота). **Верное состояние: прогон СОСТОЯЛСЯ** (строки `chunk_status` есть, снапшоты записаны) → **ПОТОМ применена правка банка** → и твой механизм отдаёт НЕнулевой счёт затронутых юнитов, тогда как сегодняшний `status` на том же состоянии отвечает нулём. Разница «сегодня ноль / после фикса N» и есть деливерабл. При этом леджер не двинулся ни на цент, а в стор не ушло ничего сверх first-touch. 4. **Свои посадки мутаций:** минимум по одной на пункт; вердикт — по ДЕЛЬТЕ против чистой базовой линии и по ТОПИЧНОСТИ упавшего теста, не по цвету батареи. 5. **Адверсариальный проход по своей готовой работе — заказан; глубину и веер выбираешь сама.** ⚠ Куда смотреть в ЭТОМ паке: деньги (перерасход либо отказ законной работе) · тождество бесплатной сметы и платного прогона · шов (форма ответа, argv). Дефекты, которые внёс сам пак, называй первыми — приёмке они не видны. ⚠ **Fable 5** — обычно хватает одного-двух агентов, но это РЕКОМЕНДАЦИЯ, не потолок: веер под предмет выбирает сессия. Модель задавай ЯВНО и знай, сколько их работает. ## §5. Оси ревью — 1–3, вправе заменить с аргументом 1. **Ось денег:** может ли новый потолок дать перерасход или, наоборот, отказать законной работе; не сломана ли кумулятивная семантика денежного капа. 2. **Ось «пара, которой в репо нет»:** ветвления по паре/книге в Go быть не должно. 3. **Ось детерминизма:** трогаешь сборку запроса или снапшот — не перекупается ли прогон. ## §6. Записка-план, комплектность **До правок** — записка-план в своей секции журнала. **В конце** — таблица комплектности против §3: пункт → что сделано → каким ИСПОЛНЕНИЕМ подтверждено. Слово вместо команды = пункт НЕ сделан. ## §7. Эхо-протокол старта ДО работы — ≤10 строк: **скоуп · инварианты · не-делать**. Первое действие — вписать свой блок в файл канала (§10), сразу второе — послать мне эхо. ## §8. Obstacle — обязательная секция **«Что НЕ удалось и что НЕ проверено»** отдельной секцией, не россыпью. ## §9. Канал вопросов и твоё право отказаться Конфликт промта с кодом или доками — **пинг, не интерпретация в свою пользу**. ⚠ **У тебя есть право сказать «этого делать не надо» — с аргументом.** За последние сутки исполнители опровергли ЧЕТЫРЕ моих заказа и все четыре раза были правы; два я пере-проверил своей посадкой и отозвал. Отказ с разбором дороже послушного исполнения. ⚠ **Тесты и гейты не подгонять под зелень** (D39.121). ## §10. Как со мной связаться **Адрес — в файле `/tmp/textmachine-channel`.** Впиши свой блок первым делом, чужие не трогай. ⚠ **Канала нет ⇒ НЕ искать:** вопрос секцией в отчёт, работа продолжается.