18 KiB
Промт: бэкенд-сессия «эмиттер шва» — строки 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).
Обязательное чтение до кода (порядок; НЕ грепать — читать)
docs/architecture/16-events-emitter.mdЦЕЛИКОМ — сборка-норматив этого пака: там ратифицированная форма, контракт декодера, деньги, открытые развилки (§5), чек-лист входного чтения (§6 — исполнить ВСЕ 14 позиций, включая PD-строки реестра платформы и код читателя) и оговорки сборки (§7). При конфликте нормдока с D-логом побеждает D-лог.docs/research/25-seam-cold-review.md— механизмы и отвергнутые альтернативы («чтобы отвергнутое не вернулось»);docs/architecture/15-money-path.md— деньги от гранта до settle.- 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, греп номера. - Код читателя (чужая зона
platform/— ЧИТАТЬ можно и нужно, писать НЕЛЬЗЯ):platform/internal/ingest/events.go(словарь-предложение) ·decoder.go·tail.go. - Своя сторона:
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.