textmachine/docs/BACKEND_EMITTER_SESSION_PROMPT.md

166 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Промт: бэкенд-сессия «эмиттер шва» — строки 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 пп.12 на
поверхности расходятся («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.