textmachine/docs/BACKEND_EMITTER_SESSION_PROMPT.md

18 KiB
Raw Blame History

Промт: бэкенд-сессия «эмиттер шва» — строки 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/1failed.
  • День-потолок (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.