25 KiB
Промт: бэкенд, пак «деньги» — продажа обретает настоящий стоп, а смета становится доступной до покупки
Выдан оркестратором №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 позиций, ЗАКОН
backend/cmd/tmctl/main.go:252-262— три денежных флага и почему они ОРТОГОНАЛЬНЫ; там же предупреждение «--ceiling-usd— НЕ бюджет прогона» ·invocation.go:120-210(парсинг и валидация флагов).backend/internal/store/ledger.go:30-80— где потолок реально судит трату (Reserve).backend/internal/pipeline/rebill.go—RebillProjection,projectRebill(:111), порог согласия и его текст отказа (:318-324).backend/internal/pipeline/status.go:733-744—projectStoredMemory; читать ВНИМАТЕЛЬНО: это и есть слепое окно из §0.2.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. Самопроверка ИСПОЛНЕНИЕМ — «перечитал сам» её не удовлетворяет
- Батарея зоны под
-race, линтер 0 issues; числа — с командами. - Потолок объёма доказывается ПОСТ-ФИКСНЫМ инвариантом: прогон с потолком в N единиц останавливается на N, отвечает признаком «встал по ОБЪЁМУ» (отличимым от денежного), и останавливается ПЕРЕД началом N+1, а не после.
- ⛔ Смета доказывается $0 — и РЕПРО ПЕРВОЙ РЕДАКЦИИ БЫЛО ВЫРОЖДЕННЫМ, я повторил ошибку, о
которой сам же написал эрратy 28.08-и. Состояние «
translateне запускался» негодно: у такой книги строкchunk_statusнет вовсе, иprojectRebillотдаёт ПУСТО и до фикса, и после (rebill.go:132-134пропускает строки без снапшота). Верное состояние: прогон СОСТОЯЛСЯ (строкиchunk_statusесть, снапшоты записаны) → ПОТОМ применена правка банка → и твой механизм отдаёт НЕнулевой счёт затронутых юнитов, тогда как сегодняшнийstatusна том же состоянии отвечает нулём. Разница «сегодня ноль / после фикса N» и есть деливерабл. При этом леджер не двинулся ни на цент, а в стор не ушло ничего сверх first-touch. - Свои посадки мутаций: минимум по одной на пункт; вердикт — по ДЕЛЬТЕ против чистой базовой линии и по ТОПИЧНОСТИ упавшего теста, не по цвету батареи.
- Адверсариальный проход по своей готовой работе — заказан; глубину и веер выбираешь сама. ⚠ Куда смотреть в ЭТОМ паке: деньги (перерасход либо отказ законной работе) · тождество бесплатной сметы и платного прогона · шов (форма ответа, argv). Дефекты, которые внёс сам пак, называй первыми — приёмке они не видны. ⚠ Fable 5 — обычно хватает одного-двух агентов, но это РЕКОМЕНДАЦИЯ, не потолок: веер под предмет выбирает сессия. Модель задавай ЯВНО и знай, сколько их работает.
§5. Оси ревью — 1–3, вправе заменить с аргументом
- Ось денег: может ли новый потолок дать перерасход или, наоборот, отказать законной работе; не сломана ли кумулятивная семантика денежного капа.
- Ось «пара, которой в репо нет»: ветвления по паре/книге в Go быть не должно.
- Ось детерминизма: трогаешь сборку запроса или снапшот — не перекупается ли прогон.
§6. Записка-план, комплектность
До правок — записка-план в своей секции журнала. В конце — таблица комплектности против §3: пункт → что сделано → каким ИСПОЛНЕНИЕМ подтверждено. Слово вместо команды = пункт НЕ сделан.
§7. Эхо-протокол старта
ДО работы — ≤10 строк: скоуп · инварианты · не-делать. Первое действие — вписать свой блок в файл канала (§10), сразу второе — послать мне эхо.
§8. Obstacle — обязательная секция
«Что НЕ удалось и что НЕ проверено» отдельной секцией, не россыпью.
§9. Канал вопросов и твоё право отказаться
Конфликт промта с кодом или доками — пинг, не интерпретация в свою пользу. ⚠ У тебя есть право сказать «этого делать не надо» — с аргументом. За последние сутки исполнители опровергли ЧЕТЫРЕ моих заказа и все четыре раза были правы; два я пере-проверил своей посадкой и отозвал. Отказ с разбором дороже послушного исполнения. ⚠ Тесты и гейты не подгонять под зелень (D39.121).
§10. Как со мной связаться
Адрес — в файле /tmp/textmachine-channel. Впиши свой блок первым делом, чужие не трогай.
⚠ Канала нет ⇒ НЕ искать: вопрос секцией в отчёт, работа продолжается.