# Промт: бэкенд-сессия «эмиттер шва» — строки 103 + 135 + 165 (события `events.jsonl` + деньги шва) Ты — бэкенд-сессия TextMachine. Зона записи — **только `backend/`**; сессия НЕ коммитит — дерево готовит и передаёт на лендинг оркестратору. Онбординг по `CLAUDE.md` обязателен (там же гардрейлы: `.env` не читать, git-нормы, запрет подгонки тестов). В дереве живут незакоммиченные файлы ЧУЖИХ зон — полигон (`eval/*`, `docs/experiments/*`, у него прямо сейчас ЖИВОЙ ПЛАТНЫЙ прогон: ничего в `eval/` и `~/books/` не запускать, замки не трогать) и, возможно, фронт (`frontend/*`). Начни с `git status` и опознай чужое; чужие зоны не трогать и не «прибирать». Пинги и итоги — `docs/PROGRESS.md`, секция «Бэкенд», ТОЛЬКО append своей секции (CURRENT-STATE не касаться — файл держат параллельные сессии). Пак **$0**: ни одного платного вызова провайдеров; всё на фикстурах и фейках. > **Онбординг-блок (проблема, которую решаем).** Движок и платформа разведены швом: движок — > транзиентный systemd-юнит на прогон, платформа спавнит его и читает события. Платформенная > сторона шва ПОСТРОЕНА и залендена (P4, D39.123): тейлер `events.jsonl` с курсором > `(engine_run_id, seq)`, декодер словаря, реконсилятор, карантин проекции — всё это ЖДЁТ файла, > который движок ещё не пишет (`platform/internal/ingest/tail.go:16-17` «the engine's event > journal, in the BOOK's directory»; `ErrNoJournal` не ошибка — тейлер спокойно ждёт). Пока файла > нет, платформа живёт на редком `status --json`-резюнке (5 минут, каждый вызов = секунды CPU > пере-ингеста), а стоп по потолку неотличим от аварии: движок отдаёт потолок ошибкой > (`errReserveCeiling`), exit-код мапит всё в 1, и платформа честно ставит `failed` там, где > контракт требует `paused` — это **PD-113, единственный открытый major платформы**, и цена его > выросла после PD-158 (потолок платформы выставляется впритык к холду, стоп по потолку = ровно > исчерпание холда). Эта сессия строит движковую сторону: эмиттер журнала (строка 103), > различимые exit-коды (строка 165) и гейт потолка пер-вызов (строка 135). ## Обязательное чтение до кода (порядок; НЕ грепать — читать) 1. **`docs/architecture/16-events-emitter.md` ЦЕЛИКОМ** — сборка-норматив этого пака: там ратифицированная форма, контракт декодера, деньги, открытые развилки (§5), чек-лист входного чтения (§6 — исполнить ВСЕ 14 позиций, включая PD-строки реестра платформы и код читателя) и оговорки сборки (§7). При конфликте нормдока с D-логом побеждает D-лог. 2. `docs/research/25-seam-cold-review.md` — механизмы и отвергнутые альтернативы («чтобы отвергнутое не вернулось»); `docs/architecture/15-money-path.md` — деньги от гранта до settle. 3. D-ноты: D39.106 (форма шва) · D39.119 п.3 (at-least-once) · D39.122 п.2(б,в) (`done` счётчиков, семантика `--ceiling-usd`) · D39.123 п.2(б,в,ж) (PD-158, карантин ПРОЕКЦИИ, цена PD-113) — живой файл `docs/architecture/05-decisions-log.md`, греп номера. 4. Код читателя (чужая зона `platform/` — ЧИТАТЬ можно и нужно, писать НЕЛЬЗЯ): `platform/internal/ingest/events.go` (словарь-предложение) · `decoder.go` · `tail.go`. 5. Своя сторона: `backend/internal/obs/logging.go:15-54` · `backend/internal/pipeline/stagerun.go` (потолок/деньги, unit-done) · `backend/internal/store/` (транзакции чекпойнтов, леджер) · `backend/cmd/tmctl/main.go:30-52` (exit-контракт) · `backend/README.md` · `docs/architecture/12-go-style-notes.md` §0 (норматив общности). По всем задачам — прогон в отчёте; **молча пропустить задачу нельзя**: сделано / диспозиция с обоснованием / вопрос пингом. ## Задачи ### 1. Дизайн-развилки — решить и ЗАПИСАТЬ до кода Все четыре названы нормдоком §5/§7; каждую решить с обоснованием в отчёте (форма — решение + отвергнутые варианты + почему): - **Формат журнала:** одиночный JSONL против пофайловых событий / CRC-фрейминга. ⚠ Читатель платформы уже построен под одиночный `events.jsonl`; выбор другой формы = согласованное изменение ОБЕИХ сторон — только пингом через оркестратора, не молча. - **Политика «журнал не пишется»** (PD-60): fsync-политика, ENOSPC, заполненный диск — блокировать прогон или деградировать с громким флагом; «обе позиции законны, молчаливой третьей нет». - **fsync каталога** при append/rename на ext4 (durability, NOTE D39.122). - **Как «строка события в той же SQLite-транзакции, что чекпойнт» ложится на текущие транзакции `internal/store`** — это ратифицированная ФОРМУЛА (outbox, не вторая запись), не факт кода; первая проектная работа сессии (нормдок §7 п.2). ### 2. Эмиттер `events.jsonl` (строка 103) - `hello` первой строкой процесса: `seq=1`, `stream_version`, `engine_run_id`, `book_id`, `chunker_version`; версионирование по правилу terraform (минор = ignore-unknown, мажор = reject). Резюм — НОВЫЙ процесс: дописывает второй `hello` в тот же файл, свой `engine_run_id`, `seq` заново с 1 (контракт читателя: `events.go:62-70`, `tail.go:58-60`). - Плотный `seq` без дыр; **строка повторима байт-в-байт** (читатель сверяет дубли по sha256 сырой строки — пере-генерация с другим timestamp = `ErrPayloadConflict` = карантин проекции). - Словарь событий: `progress` (пофазные draft/edit, семантика `done` = «разрешено волной») · `unit_done` · `bank_stop` (`terms_proposed`; полная таблица — артефактом, не потоком) · `ceiling` · `spend` · `finished`. Форма `events.go` — **ПРЕДЛОЖЕНИЕ платформы, записанное кодом**, «so the engine zone can answer it with a diff»: движок вправе ответить диффом (состав полей, `eta_seconds`, доп-события), расхождения — пингом в отчёте, не молча. ⚠ research/23 §2 со словарём НЕ сверялся (нормдок §7 п.1) — свериться при дизайне. - Деньги: только внутри `spend`, **кумулятивно, целыми micro-USD** (никаких float/строковых форм — урок PD-79); enforcement на событиях запрещён (доккоммент `Spend`); в INFO-логи и argv деньги и book id не текут (PD-99, D39.84). - **Сброс буфера на всех путях выхода** (PD-61а): `os.Exit`/`log.Fatal` пропускают defer — финальные события (`finished`, `ceiling`) теряться не должны; краш-пути перечислить и покрыть. - Человеческие логи остаются на stderr как есть; эмиттер их не заменяет и не парсит. ### 3. Событие потолка + различимые exit-коды (строка 165, закрывает PD-113 и PD-196) - `ceiling` — ФАКТ без цифр (деньги не выходят из движка нигде, кроме `spend`); эмитится на `errReserveCeiling`-стопе (сейчас деньги только в тексте ошибки `stagerun.go:489-513`). - Новые exit-коды tmctl: потолочная остановка и graceful stop (SIGTERM пойман, `NotifyContext`) обязаны стать различимы между собой и от infra-failure; 0/2/3 заняты (clean · completed-with-flags · банк-стоп) — выбрать значения, внести в shell-контракт `main.go` и задокументировать. Помнить PD-152 платформы: сегодня пойманный SIGTERM выходит кодом 1, и ExecStopPost-маркер честно пишет `exited/1` → `failed`. - День-потолок (`day_usd` из book.yaml) останавливает прогон невидимо для платформы (PD-157) — реши дёшево: причина потолка (book/day) внутри события `ceiling` либо диспозиция «не сейчас» с обоснованием. - **PD-196 платформы (мандат D39.130 п.3, тем же паком):** exit 1 сегодня не различает и классы отказа `manifest`-пути — «источник нечитаем» / «конфиг битый» / «лок занят»; платформенный интейк чуть не удалил файл пользователя за опечатку оператора, потому что все трое выглядят одинаково. Сделай классы отказа различимыми тем же shell-контрактом (exit-код или машиночитаемая причина в событии/`status --json` — выбери форму и задокументируй в `main.go`); минимум — три названных класса, расширяемость словарём, не перечислением в платформе. ### 4. Гейт потолка на каждый платный вызов (строка 135) Сначала выясни фактическую границу по коду: `docs/architecture/15-money-path.md` §2 пп.1–2 на поверхности расходятся («Reserve перед каждым платным вызовом сверяет потолки», `ledger.go:44,58,63` — против «гейт сегодня на границе юнита работы, ужесточение — строка 135»). Разбери, что именно проверяется и когда останавливается (`stagerun.go:479-511`), и ужесточи до пер-вызовного гейта по дизайну research/25 §Деньги. Границу «было/стало» записать в отчёт числом и сценарием. ⚠ Потолки — wiring, не семантика: `Ceilings` вне `BriefHash` (`book.go:262-265`) — снапшот, ре-билл и голден двигаться НЕ должны; это же относится ко всему паку (см. приёмку). ### 5. Дешёвый довесок, только если ложится чисто: внешний trace-контекст (строка 102) `TraceID` минтится заново каждым вызовом (`cmd/tmctl/main.go:67-70`), а прогон, запущенный платформой, должен быть одной трассой; `engine_run_id` из `hello` — родня этой темы. Если приём внешнего trace-id (флаг/ENV) ложится единичным касанием — взять; если тянет рефакторинг — диспозиция «не в этом паке», строка 102 остаётся. ## Что НЕ делать - Эскроу/`uncertain`/`closing` (строка 136), ночная сверка с провайдером (137), money-путь BilledDecodeFails (78), оценка $/глава (166) — чужие паки. - Реконсилятор, пиннинг бинаря, тейлер, декодер — построены платформой; её зону НЕ править (словарь согласуется диффом-предложением в отчёте + пингом). - Никакого HTTP/сервера в движке (D39.81); живой SQLite наружу не отдавать; stdout-дисциплину не ломать. - Пар- и книго-специфики в событиях НЕТ (общность §0.1: ревью-вопрос «заработает ли пара, которой в репо нет, без правки Go» — обязан отвечаться «да» и для событий). - Тесты, голден и гейты не подгонять под зелень (D39.121); несогласие с тестом — вопрос оркестратору пингом. - `git add`/`git commit` не делать — дерево передаётся на лендинг как есть. ## Отчёт и приёмка - **Батарея зоны целиком EXIT=0** (включая `-race`); счётчик тестов вырос, удалённых ноль. - Тесты пака, каждый падает на до-фиксном/сломанном коде: транзакционность outbox (**crash-тест: kill -9 посреди волны → журнал без дыр и расхождений с SQLite**, догоняется честно) · плотность seq и hello-первый · байт-повторимость строки при реплее · сброс буфера на аварийном выходе · событие потолка на живом стопе по потолку (фикстурные цены) · новые exit-коды живым бинарём (SIGTERM и потолок). - **Сквозная проба с настоящим читателем:** скормить свой `events.jsonl` реальному декодеру/тейлеру платформы — go test в КОПИИ зоны `platform/` ВНЕ рабочего дерева (норма D22 п.10: transient-прогоны в чужой зоне — только в копии); hello/seq/типы принимаются, подделка payload на том же seq даёт `ErrPayloadConflict`. - **Голден/снапшот-нейтральность доказать**: дифф голдена пуст, `RequestHash`/снапшот/подпись банка не двинулись — эмиттер это наблюдаемость, не семантика. - **Мандат самопроверки:** ревью ИСПОЛНЕНИЕМ своего кода и своих проб + адверсариальное ревью диффа (author≠reviewer) в конце; находки с диспозициями — в отчёт. После тяжёлого ревью — записка-план «ID находки → статус → улика» и движение по ней (D39.121). - **Предметные оси самопроверки** (D39.120): (1) общность §0.1 — события без пар-ветвлений; (2) деньги и детерминизм — события не двигают хеши/снапшот, повторимы, целочисленны; (3) транзакционная целостность outbox — перечислить craш-точки и показать покрытие каждой. - Клеймы прогресса грунтовать исполнением (число = команда рядом); перед сдачей перечитай последний абзац отчёта — обещаний «сделаю» в нём быть не должно; комплектность против этого промта — механически, пункт за пунктом. - **Канал вопросов — пинг в секцию «Бэкенд», и у сессии есть право оспорить ПОСЫЛКУ** (D39.47 п.4): замер или чтение кода, бьющие по основанию задачи (не по исполнению), обязывают остановиться и поднять вопрос — а не выдать формально требуемый артефакт; тихая интерпретация запрещена. - **Самоверификация интервально, не одним прогоном в конце:** после каждой задачи — сверка с промтом и прогон батареи; красное чинится до перехода к следующей. - Отчёт: дизайн-решения §1 · дифф словаря против `events.go` · таблица задач · пинг в `docs/PROGRESS.md` секцию «Бэкенд». Хендофф-артефакты — только durable-пути, не scratchpad.