textmachine/docs/BACKEND_MONEY_PACK_SESSION_PROMPT.md

25 KiB
Raw Blame History

Промт: бэкенд, пак «деньги» — продажа обретает настоящий стоп, а смета становится доступной до покупки

Выдан оркестратором №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:67bookTotal+estimate > c.BookUSD). Про число глав движок не знает вовсе, флага такого нет.

Измерено приёмкой 28.08 (леджеры реальных прогонов, $0): настоящая цена главы — средняя $0.01150.0190, максимум одной главы $0.0375; константа продажи платформы — $0.03. То есть покупка «10 глав» отдаёт движку $0.30, а $0.30 на этом материале покупает 1626 глав. Пользователь просит одно, получает другое, и полоса каппится на купленном.

И хвост цены порождает КАЧЕСТВО, а не объём (наблюдение владельца, подтверждено данными): вызов может отработать успешно и вернуть мусор — деньги списаны, юнит уезжает на ретрай, иногда на эскалационный хоп к дорогой модели. В леджере это видно с именами причин (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.goRebillProjection, projectRebill (:111), порог согласия и его текст отказа (:318-324).
  4. backend/internal/pipeline/status.go:733-744projectStoredMemory; читать ВНИМАТЕЛЬНО: это и есть слепое окно из §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. Оси ревью — 13, вправе заменить с аргументом

  1. Ось денег: может ли новый потолок дать перерасход или, наоборот, отказать законной работе; не сломана ли кумулятивная семантика денежного капа.
  2. Ось «пара, которой в репо нет»: ветвления по паре/книге в Go быть не должно.
  3. Ось детерминизма: трогаешь сборку запроса или снапшот — не перекупается ли прогон.

§6. Записка-план, комплектность

До правок — записка-план в своей секции журнала. В конце — таблица комплектности против §3: пункт → что сделано → каким ИСПОЛНЕНИЕМ подтверждено. Слово вместо команды = пункт НЕ сделан.

§7. Эхо-протокол старта

ДО работы — ≤10 строк: скоуп · инварианты · не-делать. Первое действие — вписать свой блок в файл канала (§10), сразу второе — послать мне эхо.

§8. Obstacle — обязательная секция

«Что НЕ удалось и что НЕ проверено» отдельной секцией, не россыпью.

§9. Канал вопросов и твоё право отказаться

Конфликт промта с кодом или доками — пинг, не интерпретация в свою пользу. ⚠ У тебя есть право сказать «этого делать не надо» — с аргументом. За последние сутки исполнители опровергли ЧЕТЫРЕ моих заказа и все четыре раза были правы; два я пере-проверил своей посадкой и отозвал. Отказ с разбором дороже послушного исполнения. ⚠ Тесты и гейты не подгонять под зелень (D39.121).

§10. Как со мной связаться

Адрес — в файле /tmp/textmachine-channel. Впиши свой блок первым делом, чужие не трогай. ⚠ Канала нет ⇒ НЕ искать: вопрос секцией в отчёт, работа продолжается.