166 lines
18 KiB
Markdown
166 lines
18 KiB
Markdown
# Промт: бэкенд-сессия «эмиттер шва» — строки 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.
|