232 lines
25 KiB
Markdown
232 lines
25 KiB
Markdown
# Промт: бэкенд, пак «деньги» — продажа обретает настоящий стоп, а смета становится доступной до покупки
|
||
|
||
> **Выдан оркестратором №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`.** Впиши свой блок первым делом, чужие не трогай.
|
||
⚠ **Канала нет ⇒ НЕ искать:** вопрос секцией в отчёт, работа продолжается.
|