Ratify D39.85: industry-standard engine-platform seam recorded in research 23, event stream plus materialization plus artifacts, anti-patterns banned, emitter row opened
This commit is contained in:
parent
fbefe33b1f
commit
07a8d97517
5 changed files with 92 additions and 6 deletions
|
|
@ -1,8 +1,8 @@
|
|||
# Журнал прогресса
|
||||
|
||||
> **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-08-02, голова D39.84). **Источник истины по РЕШЕНИЯМ — `architecture/05-decisions-log.md`; этот файл — ЖУРНАЛ.**
|
||||
> **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-08-02, голова D39.85). **Источник истины по РЕШЕНИЯМ — `architecture/05-decisions-log.md`; этот файл — ЖУРНАЛ.**
|
||||
> - **Сделано (сводно; детали — D-лог и архив-слайсы):** Ф0 ✅ · Ф1-инфра ✅ · арх-ресет D39 (7 слоёв, паки 11–16) ✅ · паки 17 «канал B» · 18 «долги» · 19 «голос/состояние» · 20 «банк+терминолог» ✅ (D39.26–28/31/41–56) · мини-прогон (D39.37) и ХОЛОДНЫЙ прогон (D39.58: recall банка 0.918/0.980, банк приходит переведённым) приняты · полигон-пакеты 5–8, ToS-речек (D39.57), Р6+P4 (D39.61) закрыты · **карта языковой привязки движка (D39.60): 149 сайтов/45 файлов, книго-ось чиста (4), ja→ru безопасно без правки Go, en→ru — нет** · Р1–Р4 закрыты целиком (D39.63: Р2 = ЗНАЧЕНИЕ, Р4 = норма потолка recall) · **ФАЗА 2 ОБЩНОСТИ ✅ (D39.64: П0 эмбед-хеш · П1 цель-шов · П2 скрипт-шов, ko-баг закрыт · П3 нарезка · П4 манифест-по-каналам; голден вердикт-нейтрален 0/172, майнер-парити EXACT)** · жанровый словарь отменён как класс (D39.47) · петля ремонта построена и НЕ включена (D39.38).
|
||||
> - **Курс (D39.59–78): ОБЩНОСТЬ ✅ → КАЧЕСТВО БАНКА ✅ (D39.69/75/77) → ПАКЕТ-ЧЕКЕРОВ ✅ (D39.78: строка 25 целиком; харнесс labels В GIT 12/12; K2 r0.85 · K4b r0.22 · K6 fp 14→6; Р2 hard/soft + Р4-потолки в контрактах).** **Текущее: МЕЛКАЯ ПАЧКА ✅ ПРИНЯТА И ЗАЛЕНДЕНА (D39.82: строки 89/53/83 закрыты · 93 сужена до остатков · F4-бэкап+pre-flight построен · register/жанр → книжный слой · DC7 пере-гейчен сид-покрытием → строка 98); решения §6 исполнены (D39.79/82); **РЕ-ПРОБА + ОТЛАДОЧНЫЙ ХОЛОДНЫЙ ПРОГОН coldrun-b САНКЦИОНИРОВАНЫ (D39.83: промт выдан и холодно аудирован — 3 major вправлены; потолки $0.15 ре-проба / $5 прогон; ступени 2+8 глав, автобанк по D39.77)**; ре-проба flash (строка 74) — по слову владельца; finding-1 ЗАКРЫТ (D39.77); онбординг-оверхол доков исполнен (D39.80); **ФРОНТ ОТКРЫТ владельцем (D39.81: продукт = SaaS; зоны `frontend/` + `platform/` живые, фронт работает на моках; движковая очередь НЕ пере-упорядочена — ранняя вставка одна: строка 95 «контракт API» $0; строки 96/97 и перевесы 49/94 заведены)**; **СТЕК ФРОНТА/ПЛАТФОРМЫ ЗАПИНЕН, 8 решений владельца ратифицированы (D39.84: SEO Google-only · PWA-первый · денег в MVP-UI нет · стоп-на-банке = параметр запуска, `--verify-bank` готов · перевод НИКОГДА на индексируемый URL = ПТ-34 · якорь чтения = edit-unit, ПТ-21; работы движка под фронт = строки 99–102; фронт/платформа ведут ЗОННЫЕ бэклоги — `frontend/docs/BACKLOG.md` · `platform/BACKLOG.md`, строки 96/97 → П-1/П-2; зоны залендены, фронт-промт S0–S7 в `frontend/docs/`)**.** ⚠ **DeepSeek-V4-Flash-0731: платные прогоны СТОП до ре-пробы (строка 74; там же 6/6-порог классификатора и слоты Q2/Q7). Перекупок нет (D39.63).**
|
||||
> - **Курс (D39.59–78): ОБЩНОСТЬ ✅ → КАЧЕСТВО БАНКА ✅ (D39.69/75/77) → ПАКЕТ-ЧЕКЕРОВ ✅ (D39.78: строка 25 целиком; харнесс labels В GIT 12/12; K2 r0.85 · K4b r0.22 · K6 fp 14→6; Р2 hard/soft + Р4-потолки в контрактах).** **Текущее: МЕЛКАЯ ПАЧКА ✅ ПРИНЯТА И ЗАЛЕНДЕНА (D39.82: строки 89/53/83 закрыты · 93 сужена до остатков · F4-бэкап+pre-flight построен · register/жанр → книжный слой · DC7 пере-гейчен сид-покрытием → строка 98); решения §6 исполнены (D39.79/82); **РЕ-ПРОБА + ОТЛАДОЧНЫЙ ХОЛОДНЫЙ ПРОГОН coldrun-b САНКЦИОНИРОВАНЫ (D39.83: промт выдан и холодно аудирован — 3 major вправлены; потолки $0.15 ре-проба / $5 прогон; ступени 2+8 глав, автобанк по D39.77)**; ре-проба flash (строка 74) — по слову владельца; finding-1 ЗАКРЫТ (D39.77); онбординг-оверхол доков исполнен (D39.80); **ФРОНТ ОТКРЫТ владельцем (D39.81: продукт = SaaS; зоны `frontend/` + `platform/` живые, фронт работает на моках; движковая очередь НЕ пере-упорядочена — ранняя вставка одна: строка 95 «контракт API» $0; строки 96/97 и перевесы 49/94 заведены)**; **СТЕК ФРОНТА/ПЛАТФОРМЫ ЗАПИНЕН, 8 решений владельца ратифицированы (D39.84: SEO Google-only · PWA-первый · денег в MVP-UI нет · стоп-на-банке = параметр запуска, `--verify-bank` готов · перевод НИКОГДА на индексируемый URL = ПТ-34 · якорь чтения = edit-unit, ПТ-21; работы движка под фронт = строки 99–102; фронт/платформа ведут ЗОННЫЕ бэклоги — `frontend/docs/BACKLOG.md` · `platform/BACKLOG.md`, строки 96/97 → П-1/П-2; зоны залендены, фронт-промт S0–S7 в `frontend/docs/`)**; **ШОВ ДВИЖОК↔ПЛАТФОРМА РАТИФИЦИРОВАН ПО РЕСЁРЧУ (D39.85, `research/23`): NDJSON-поток событий → материализация в Postgres → SSE; артефакты границ стадий; анти-паттерны запрещены (живой SQLite не читать · HTTP в движок не тащить · read-пакет из ядра не выносить); строка 103 заведена, входы контракта 95 зафиксированы)**.** ⚠ **DeepSeek-V4-Flash-0731: платные прогоны СТОП до ре-пробы (строка 74; там же 6/6-порог классификатора и слоты Q2/Q7). Перекупок нет (D39.63).**
|
||||
> - **Горизонт (D39.62/67):** ре-проба flash (74; + платный порог 6/6 классификатора + пробы 36б-замера Q2/эмиссии Q7 по слову) → **ДОБОР ИДЕАЛА** (первым прогоном: оси голоса 24 · авто-режим · итерация №2 редакторов 65 · цена 16 · веса K1–K12 13а · вне-претрейн чекпоинт 55) → ВТОРАЯ ПАРА живьём (ja→ru; преп 81) → МАСШТАБ → пилот Ф2.5 (гейт резюме-строки 80; строки 62–68, 85) → Ф3 ридер-IDE (69–71). **Стоячие:** ToS-триггер 25.10 · Ш-2 до go1.27 · строка 74 перед любым платным прогоном.
|
||||
> - **Стек:** draft deepseek-v4-flash thinking-ON (+банкнота) **⚠0731** → терминолог (та же модель, батчи, экран `target_script`) → editor deepseek-v4-pro БИЛИНГВ ИНТЕРИМ (вендором НЕ тронут; glm-5 резерв) → судья gemini (Ф2, полигон); канал B Mistral+grok; ~$0.85/ранобэ (D30.4) — пере-калибровка после развилки 74.
|
||||
> - **ЕДИНЫЙ БЭКЛОГ — секция «Бэклог» ниже** (одна таблица, единственный трекер; каждая петля обязана иметь диспозицию: решено / отложено-с-записью / отклонено; ведёт оркестратор).
|
||||
|
|
@ -101,13 +101,18 @@
|
|||
| 70 | Action-security gate перед выдачей tools/webfetch (D25 п.5) | бэкенд | когда-нибудь | Ф3 | D39.34(7) |
|
||||
| 71 | **Планы research/22**: epub-tag-rewrite · Q7-леджер ; + F3-brief из D29 п.3 (chat-edit · Not-useful-петля · cost-of-fix) + Ф3-скоуп 02-mvp-plan (TMX/TBX · Bertalign · дистилляция 7–14B); + V5: фронт ВЕБ-ПЕРВЫЙ, SEO/ранжирование (куки · страницы-сироты · актуальные алгоритмы Google) — определяет выбор фреймворка (SSR vs SPA), в Ф3-дизайн ДО выбора стека (ПТ-23) | бэкенд | когда-нибудь | Ф3 | D39.34(7), START_PROMT V5, D39.80 |
|
||||
| 94 | **Продукт-механика входа и прогона** (V2, ПТ-16..18): абьюз/misuse-прескрин дёшево и точно ДО трат токенов (H8) + UI-контракт отказа (проект-решение D39.80) · лимит размера входа настройкой (H7) · продукт-кнопки «стоп/продолжить» поверх построенных graceful stop и redrive, абьюзивный прогон НЕ продолжать (H10); API-форма — через контракт 95 | бэкенд | к подключению фронта (ПОДТЯНУТА D39.81; лимит можно раньше, к пилоту) | отдельный пак при Ф3-препе (после 95) | START_PROMT V2, H7/H8/H10, D39.80, D39.81 |
|
||||
| 95 | **Контракт API v0 + продуктовый словарь статусов ($0, ранняя вставка):** зафиксировать артефактом ДО большого кода обеих сторон — фронт пишется на моках, без пришпиленного контракта моки и API разойдутся (класс «док↔код», 26/180 переписи); дом — `docs/architecture/14-api-contract.md`; продуктовые статусы «загрузка → разбор → перевод → подпись банка → финал → готово», слой перевода внутренних вердиктов в продуктовые (flag_reason/стадии/модели НЕ протекают — инвариант D39.81); двухфазный поток уже в движке (`--verify-bank`, банк-пауза) — ВЫВЕСТИ, не проектировать; резерв полей прогресс/ETA (54) и стоп/продолжить (94); ревью-вопрос контракта: «сменится стадия конвейера — придётся ли править фронт?»; входы D39.84: контракт подписи учитывает пересборку банка (подпись ≠ UPDATE — пишем в файлы-источники) · формы пофазного прогресса/манифеста/таблицы подписи = строки 99–101 | оркестратор/бэкенд/фронт | скоро (единственная ранняя вставка Ф3) | дизайн-промт по слову владельца | релей фронта 02.08, D39.81, research/16 |
|
||||
| 95 | **Контракт API v0 + продуктовый словарь статусов ($0, ранняя вставка):** зафиксировать артефактом ДО большого кода обеих сторон — фронт пишется на моках, без пришпиленного контракта моки и API разойдутся (класс «док↔код», 26/180 переписи); дом — `docs/architecture/14-api-contract.md`; продуктовые статусы «загрузка → разбор → перевод → подпись банка → финал → готово», слой перевода внутренних вердиктов в продуктовые (flag_reason/стадии/модели НЕ протекают — инвариант D39.81); двухфазный поток уже в движке (`--verify-bank`, банк-пауза) — ВЫВЕСТИ, не проектировать; резерв полей прогресс/ETA (54) и стоп/продолжить (94); ревью-вопрос контракта: «сменится стадия конвейера — придётся ли править фронт?»; входы D39.84: контракт подписи учитывает пересборку банка (подпись ≠ UPDATE — пишем в файлы-источники) · формы пофазного прогресса/манифеста/таблицы подписи = строки 99–101; входы D39.85 (`research/23`): (i) NDJSON-поток событий (словарь-enum + version-хендшейк + событие потолка — строка 103), (ii) артефактный контракт (export-JSON · 101 · 100), (iii) правило ре-синка (идемпотентный апсерт run_id+seq; `status --json` = канал согласования); словарь событий и продуктовый словарь статусов (ПТ-33) проектируются вместе | оркестратор/бэкенд/фронт | скоро (единственная ранняя вставка Ф3) | дизайн-промт по слову владельца | релей фронта 02.08, D39.81, research/16 |
|
||||
| 96 | **→ переехала в `platform/BACKLOG.md` П-1** (зонные бэклоги, D39.84: HTTP/SSE-слой, обвязка, аутентификация, порядок деплоя); здесь остаётся ратифицированным сам ИНВАРИАНТ D39.81 — движок = процесс-на-прогон (EXCLUSIVE flock `store.go:43`), сервер в backend НЕ пишется | платформа | — | зонный бэклог | D39.81, D39.84 |
|
||||
| 97 | **→ переехала в `platform/BACKLOG.md` П-2** (глобальный брокер рейт-лимитов; гейт «до второго параллельного пользователя» зафиксирован там же) | платформа | — | зонный бэклог | D39.81, D39.84 |
|
||||
| 99 | **Пофазный прогресс `draft N/M ∥ edit N/M`**: unit = done только при всех draft-строках членов + edit-строке (`status.go:328-331/365`) — индикатор «готово N/M» стоит 0% ВСЮ черновую волну; данные уже есть (`chunk_status` — строка-на-стадию, `migrate.go:111-126`; status уже делит стадии по волнам `status.go:332-334`) — вывод пофазных счётчиков в StatusReport/JSON без миграции схемы | бэкенд | к подключению фронта (первая по критичности из четырёх D39.84) | малое касание read-model | релей фронта 02.08, D39.84 |
|
||||
| 100 | **Персист манифеста глав/чанков** (+ `chunker_version` из снапшота + хеш источника): каждый read-вызов заново ингестит и режет исходник (`status.go:177-195` bookChunks; redrive — дважды за вызов; замер фронта 1.42–1.51 с CPU на книге 23 МБ, умножается на число книг) — нужен под экран разбора и дерево глав; дизайн обязан пережить смену чанкера (`--resnapshot`) | бэкенд | к подключению фронта | отдельное решение (дизайн с 95) | релей фронта 02.08, D39.84 |
|
||||
| 101 | **Машиночитаемая таблица подписи банка**: кап 20 — только stdout (`render.go:98`), полная таблица — текстовый сайдкар `.bank-stop.txt`, JSON/структурного выхода нет (mined-signature.yaml — сид-дельта без freq/spread/evidence); нужна фронту S5; помнить ловушку «подпись ≠ UPDATE» (банк пересобирается: `seeding.go:18/110`, `glossary.go:158-162` — контракт подписи пишет в файлы-источники) | бэкенд | к подключению фронта | малое касание (форма — с контрактом 95) | релей фронта 02.08, D39.84 |
|
||||
| 102 | **Приём внешнего trace-контекста в tmctl**: TraceID минтится заново каждым вызовом (`main.go:67-70`; внутри вызова трасса цельная, `request_log.trace_id` есть) — прогон, запущенный платформой, должен быть одной трассой | бэкенд | когда-нибудь (Ф3, с 96) | малое касание | релей фронта 02.08, D39.84 |
|
||||
| 103 | **NDJSON-эмиттер прогонных событий со стабильным словарём** (машинный канал живого статуса для платформы, `research/23` §2): NDJSON-логи уже есть (`LOG_FORMAT=json`, авто-оси trace/book/chapter/stage/role — `obs/logging.go:15-54`), но сообщения прозой, version-хендшейка нет, а **событие потолка отсутствует вовсе** (деньги только в тексте ошибки `stagerun.go:472-494`); нужен словарь-enum + version-хендшейк первой строкой (образец terraform -json: минор = ignore-unknown, мажор = reject) + событие потолка; все границы — единичные call-sites в pipeline рядом с готовыми slog; человеческие логи уже на stderr — stdout-дисциплина соблюдена | бэкенд | к подключению фронта (словарь — вместе с контрактом 95) | малое касание | research/23, D39.85 |
|
||||
## Оркестратор №9 — ШОВ ДВИЖОК↔ПЛАТФОРМА: РЕСЁРЧ ЗАПИСАН, НАПРАВЛЕНИЕ РАТИФИЦИРОВАНО (D39.85), 02.08
|
||||
|
||||
По слову владельца («двигаемся в сторону стандарта индустрии, запиши что узнал аргументированно»). НОВЫЙ [research/23-engine-platform-seam.md](research/23-engine-platform-seam.md) — 5-агентный воркфлоу по первоисточникам (BEP/buildx/terraform/CI-раннеры · Go-модули с ЖИВЫМИ сборками internal-правил · SQLite-граница · репо-инвентаризация, арбитраж 9/9 подтверждён). Форма шва: версионированный NDJSON-поток событий движка → материализация воркер-обёрткой в Postgres (Reporting Database, НЕ полный CQRS) → SSE и чтения только из Postgres; артефакты границ стадий; `status --json` = пост-фактум/ре-синк. Анти-паттерны запрещены: живой SQLite платформой не читать (локи+checkpoint starvation+схемная связка+единодушие индустрии) · HTTP в движок не тащить (движок эмитит — адаптер пушит) · read-пакет из ядра не выносить (NewReadOnlyRunner тянет 12/14 internal-пакетов — тонкого среза нет) · неверсионированный JSON не эмитить. Модули: sibling-структура подтверждена (честный аргумент — изоляция MVS-графа = детерминизм); ревью-гард: пути Go-модулей платформы/фронта не вкладывать под путь движка. НОВАЯ строка 103 (эмиттер со словарём; не хватает только словаря-enum, version-хендшейка и события потолка); входы контракта 95 дополнены тройкой поток/артефакты/ре-синк.
|
||||
|
||||
## Оркестратор №9 — СТЕК ФРОНТА/ПЛАТФОРМЫ ПРИНЯТ И ЗАЛЕНДЕН, ВОСЕМЬ РЕШЕНИЙ ВЛАДЕЛЬЦА РАТИФИЦИРОВАНЫ (D39.84), 02.08
|
||||
|
||||
По релею отчёта фронт-сессии (стек-исследование закрыто: 15 агентов, артефакты `frontend/docs/STACK_DECISIONS.md` + `FRONTEND_SESSION_PROMPT.md` S0–S7). Приёмка: 10-агентный refute-воркфлоу — все несущие бэкенд-утверждения записки CONFIRMED по коду, 8/8 веб-пинов подтверждены первоисточниками; 4 уточнения точности (гранулярность draft-only · кап 20 = stdout-only · «4276» не выводится из артефактов — принято как замер · якорь export.go = internal/pipeline). Ратифицированы 8 решений владельца (SEO Google-only · выравнивание по единицам экспорта, `--pairs` готов · подсветка блок/глава без спанов · стоп-на-банке = параметр запуска, `--verify-bank` готов · денег в MVP-UI нет · PWA-первый · вид = замеры референса · одна сессия кука/Bearer) + ПТ-34 (перевод НИКОГДА на индексируемом URL) + переформулировка ПТ-21 (якорь = edit-unit, не чанк). Работы движка = НОВЫЕ строки 99–102 (пофазный прогресс · манифест глав/чанков · машиночитаемая таблица подписи · внешний trace-контекст), все проверены кодом. Противоречие «демон в backend» разрешено сессией верно — D39.81 удержан. Две ловушки подтверждены и заведены входами дизайна (строка 95 и П-1: подпись ≠ UPDATE; read-путь не мигрирует схему). Зоны `frontend/`+`platform/` залендены. **Зонные бэклоги (решение владельца при лендинге):** фронт и платформа ведут свои — `frontend/docs/BACKLOG.md` (Ф-1..Ф-6) · `platform/BACKLOG.md` (П-1..П-4, туда переехали строки 96/97); единый бэклог фронт/платформа-строк больше не принимает. ⚠ Норма-вопрос владельцу: промт S0 велит фронт-сессии коммитить самой — против «сессии не коммитят»; рекомендация — право коммита фронту В СВОЕЙ зоне, пост-хок ревью; до слова владельца действует старая норма.
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
- [09-target-architecture.md](architecture/09-target-architecture.md) — целевая 7-слойная архитектура (статус стройки — шапка-таблица; §3 — карта находок H/L) · [10-prompt-architecture.md](architecture/10-prompt-architecture.md) — промпт-слой · [12-go-style-notes.md](architecture/12-go-style-notes.md) — норматив общности §0 · [13-tech-debt-anchors.md](architecture/13-tech-debt-anchors.md) — якоря техдолга (справочник к бэклогу, НЕ трекер).
|
||||
- Исторические, читать через ⚠-баннеры: [01-decisions.md](architecture/01-decisions.md) (Р1–Р10) · [02-mvp-plan.md](architecture/02-mvp-plan.md) · [03-implementation-notes.md](architecture/03-implementation-notes.md) · [04-unhappy-paths.md](architecture/04-unhappy-paths.md) · [06-memory-risk-registry.md](architecture/06-memory-risk-registry.md).
|
||||
- `experiments/` — эмпирика полигона: [00-provider-quirks.md](experiments/00-provider-quirks.md) — **читать перед любым вызовом провайдера**; [08-cost-model-v2.md](experiments/08-cost-model-v2.md) — денежная модель; [09-pilot-protocol.md](experiments/09-pilot-protocol.md) — пилот Ф2.5; остальные 01–16 — отчёты закрытых экспериментов (судьба — в баннерах/D-логе).
|
||||
- `research/` — фактура ресёрчей 01–22; у принятых — ревью-шапки, часть тел под ⚠ superseded: **читай баннер прежде содержимого**. Ключевые для навигации: 15 голос · 16 ридер-IDE · 17 внешняя критика · 18 рычаги качества · 19 нарезка · 20 банк-майнинг · 21 обзор транспорта · 22 доменные харнессы.
|
||||
- `research/` — фактура ресёрчей 01–22; у принятых — ревью-шапки, часть тел под ⚠ superseded: **читай баннер прежде содержимого**. Ключевые для навигации: 15 голос · 16 ридер-IDE · 17 внешняя критика · 18 рычаги качества · 19 нарезка · 20 банк-майнинг · 21 обзор транспорта · 22 доменные харнессы · 23 шов движок↔платформа (читать перед любым кодом стыка).
|
||||
- [PROGRESS.md](PROGRESS.md) — журнал: CURRENT-STATE + **ЕДИНЫЙ БЭКЛОГ** (единственный трекер) + живой хвост хроники. НЕ источник решений.
|
||||
- Активные хендофф-промты сессий (состав обновляется при каждом лендинге — норма D39.80): [ORCHESTRATOR_SESSION_PROMPT.md](ORCHESTRATOR_SESSION_PROMPT.md) (роль/нормы; состояния не дублирует) · [POLYGON_PACKAGE4_SESSION_PROMPT.md](POLYGON_PACKAGE4_SESSION_PROMPT.md) (полигон, отложен). · [BACKEND_COLDRUN_DEBUG_SESSION_PROMPT.md](BACKEND_COLDRUN_DEBUG_SESSION_PROMPT.md) (**текущий бэкендный**: ре-проба flash + отладочный холодный прогон coldrun-b). Фронт-промт — в чужой зоне `frontend/docs/`.
|
||||
- Зоны фронта (чужие, читать при касании стыка; каждая ведёт СВОЙ зонный бэклог — единый бэклог их строк не принимает, D39.84): [../frontend/](../frontend/) — веб-интерфейс: промт фронт-сессий S0–S7 + [STACK_DECISIONS.md](../frontend/docs/STACK_DECISIONS.md) (пины версий точными числами и ловушки, сверены с вебом 02.08) + [BACKLOG.md](../frontend/docs/BACKLOG.md) · [../platform/](../platform/) — SaaS control plane: README-заглушка + [BACKLOG.md](../platform/BACKLOG.md).
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
77
docs/research/23-engine-platform-seam.md
Normal file
77
docs/research/23-engine-platform-seam.md
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
# 23 — Шов движок ↔ платформа: как индустрия интегрирует batch-движок с control plane
|
||||
|
||||
> **Ревью-шапка (оркестратор №9, 02.08.2026).** Статус: ПРИНЯТ, направление ратифицировано **D39.85**. Метод: 5-агентный воркфлоу — три веб-направления по первоисточникам (официальные доки/блоги авторов инструментов, вторичных статей в выводах нет) + репо-инвентаризация + независимый арбитраж репо-находок (все 9 вердиктов подтверждены, 3 мелкие поправки внесены сюда). Правила `internal` проверены ЖИВЫМИ сборками на go1.26.4, не пересказом доков. Стоимость $0. Вопрос ресёрча: как чисто организовать read-путь и статус-поток от движка (CLI, процесс-на-прогон, приватный SQLite под EXCLUSIVE flock) к платформе (отдельный Go-модуль, net/http + Postgres + River + SSE) — не нарушая инвариантов «сервер в backend не пишется» (D39.81) и «пользователи/квоты/HTTP не проникают в движок».
|
||||
|
||||
## §0. Решение одним экраном
|
||||
|
||||
**Делаем (три канала, каждый со своей ролью):**
|
||||
|
||||
1. **Живой статус** — движок эмитит версионированный однонаправленный NDJSON-поток событий; воркер-обёртка платформы материализует его в СВОЙ Postgres; SSE и все чтения фронта — ТОЛЬКО из Postgres. Движок никогда не опрашивается.
|
||||
2. **Массивный контент** (текст глав, банк, манифест) — машиночитаемые артефакты на границах стадий с явным контрактом.
|
||||
3. **Пост-фактум чтение и ре-синк** — `tmctl status --json` / `export` (санкционированный CLI-режим по завершённому/остановленному прогону).
|
||||
|
||||
**НЕ делаем (анти-паттерны, каждый аргументирован ниже):** платформа не читает живой SQLite движка (§4) · движок не пушит HTTP и не получает сервер (§2-направление) · публичный Go-read-пакет из ядра не выносится (§5) · человеческий вывод не парсится, неверсионированный ad-hoc JSON не эмитится (§2) · полный CQRS/event-sourcing не строится — достаточно статус-таблицы (§3).
|
||||
|
||||
## §1. Наша форма — уже индустриальная
|
||||
|
||||
«CLI-движок, спавнутый на джобу, + control plane над ним» — форма Bazel (клиент → BES-бэкенд), BuildKit (buildctl → buildkitd → внешние потребители), CI-раннеров (gitlab-runner → Rails, GH Actions runner → orchestrator), terraform (CLI → TFC/автоматизации). Ни один из них не встраивает HTTP-сервер в сам движок сборки/выполнения; все решают наш вопрос одинаково — направленным потоком событий и артефактами. D39.81 (процесс-на-прогон, сервер в platform/) ресёрчем подтверждён дословно.
|
||||
|
||||
## §2. Канал 1: версионированный NDJSON-поток событий
|
||||
|
||||
**Эталоны.** Bazel Build Event Protocol: protobuf-схема событий как DAG, три транспорта (бинарный файл / NDJSON-файл / gRPC-стрим); упорядочение — гарантией анонса («каждое событие, кроме первого, анонсировано предыдущим»), полнота — только на штатном завершении ([bazel.build/remote/bep](https://bazel.build/remote/bep)). BuildKit наружу: `docker buildx --progress=rawjson` — «режим спроектирован для чтения внешней программой», финальные метаданные отдельным `--metadata-file` ([docs.docker.com](https://docs.docker.com/reference/cli/docker/buildx/build/)).
|
||||
|
||||
**Контракт потока — шаблон `terraform -json`** ([machine-readable-ui](https://developer.hashicorp.com/terraform/internals/machine-readable-ui)): один JSON-объект на строку; **первая строка всегда version-хендшейк**; semver-обещания — «минорную версию инкрементируем для обратно-совместимых добавлений: игнорируйте незнакомые поля; мажорную — для несовместимых: отвергайте неподдерживаемую». Дисциплина stdout: в машинном режиме stdout принадлежит ТОЛЬКО потоку, человеческие логи — stderr (у нас уже так: slog пишет в stderr, `obs/logging.go`).
|
||||
|
||||
**Почему версионирование обязательно:** прецедент гниения — Docker Engine JSON-stream (`jsonmessage`): неверсионированные поля депрекейтнулись, пакеты уехали в internal, потребители сломались (moby PR #51153). Ad-hoc JSON без version-поля дрейфует всегда.
|
||||
|
||||
**Направление, а не транспорт.** CI-раннеры пушат по HTTP (gitlab: `PATCH /jobs/:id/trace` каждые ~3с, сервер даёт backpressure заголовком; GH: push статуса + heartbeat) — но они долгоживущие демоны с сетевым стеком. Нам переносима не механика, а **направление**: движок ЭМИТИТ (stdout/файл), а пушит дальше адаптер на стороне платформы. HTTP в движок не проникает.
|
||||
|
||||
**Обязательная пара к потоку — реконсиляция.** BEP-каveat: при краше анонсированные события могут не прийти — гарантия полноты действует только на штатное завершение. Следствия: (а) консюмер идемпотентен — апсерт по `(run_id, seq)`; дубликаты at-least-once нормальны (transactional-outbox-грабли задокументированы: [microservices.io/outbox](https://microservices.io/patterns/data/transactional-outbox.html)); (б) на обрыве материализация пересобирается из `status --json` и артефактов завершённых стадий — у нас они уже есть как инвариант детерминизма, готовый ре-синк-источник.
|
||||
|
||||
## §3. Канал 2: материализация = Reporting Database, НЕ CQRS
|
||||
|
||||
Паттерн «воркер пушит → платформа складывает в свою БД → UI читает только её» подтверждён работающими системами (BuildBuddy как BES-бэкенд Bazel; GitLab/GitHub — статус джоба в их Postgres, раннер никогда не опрашивается). При этом Фаулер прямо предостерегает от полного CQRS: «для большинства систем CQRS добавляет рискованную сложность» ([CQRS](https://martinfowler.com/bliki/CQRS.html)); для чтений достаточно [ReportingDatabase](https://martinfowler.com/bliki/ReportingDatabase.html) — статус-таблица, наполняемая из потока. Event-sourcing, реплей истории, отдельные write/read-модели — не строить.
|
||||
|
||||
## §4. Анти-паттерн: чтение живого SQLite движка платформой
|
||||
|
||||
Честная рамка: сам SQLite мультипроцессное чтение ПОДДЕРЖИВАЕТ (FAQ #5; WAL-читатели не блокируют писателя) — вердикт «нет» держится не на «SQLite так нельзя», а на четырёх аргументах:
|
||||
|
||||
1. **Локи.** У движка `locking_mode=EXCLUSIVE`/EXCLUSIVE flock (`store.go:43`) — читатель получит SQLITE_BUSY либо вовсе не откроет файл; ослаблять локинг движка ради платформы = обратная связность. Смешение локинг-протоколов (flock у одного, SQLite-локи у другого) — задокументированный путь к порче БД ([howtocorrupt §2.4](https://sqlite.org/howtocorrupt.html)); обходы `nolock`/`immutable` на живом файле — «incorrect results and/or SQLITE_CORRUPT» ([uri.html](https://sqlite.org/uri.html)).
|
||||
2. **Checkpoint starvation.** Долгий читатель в WAL блокирует checkpoint — «WAL file will grow without bound» ([wal.html](https://sqlite.org/wal.html)): даже «безобидное» чтение платформы операционно меняет поведение и файлы движка. Проникновение не кодом, так локами.
|
||||
3. **Схемная связка.** Чтение приватной схемы v1–v14+ = IntegrationDatabase-антипаттерн ([Fowler](https://martinfowler.com/bliki/IntegrationDatabase.html)): каждая миграция движка становится breaking change платформы; read-only НЕ амнистируется — schema coupling остаётся целиком ([microservices.io/shared-database](https://microservices.io/patterns/data/shared-database.html)).
|
||||
4. **Индустрия единодушна.** Litestream (VFS-реплики «do not open the original live SQLite file»), LiteFS (page-репликация), официальный `sqlite3_rsync` (консистентный снапшот) — даже авторы инструментов, живущие внутри WAL-механики, для потребителей строят копию, не выдают путь к живому файлу. Fossil (SCM от авторов SQLite) объявляет собственную БД «implementation detail», интерфейс — экспортируемый формат поверх. Firefox держит эксклюзив на places.sqlite — профильная БД де-факто приватна.
|
||||
|
||||
**Допустимая будущая форма «да»** (если когда-нибудь понадобится): движок САМ экспортирует консистентный снапшот (`VACUUM INTO` — механизм уже есть, F4-бэкап) в отдельный файл с явным export-контрактом (стабильные export-таблицы/вьюхи + application_id/user_version-гейт версии), платформа открывает его `mode=ro&immutable=1` — на настоящем снапшоте immutable легален и избавляет от локов вовсе. sqlite.org благословляет SQLite-файл как interchange-формат ([appfileformat](https://sqlite.org/appfileformat.html)) — но это замораживает экспортированную схему, поэтому чище узкая JSON-проекция, пока не доказана нужда в большем.
|
||||
|
||||
**CLI-как-API** (сабпроцесс-на-запрос) легитимен по трём критериям из первоисточников: явный machine-режим с обещанием стабильности (git `--porcelain`), версионированная схема (terraform `-json format_version`), герметичность вызова (kubectl conventions: только машинные форматы, никакого неявного состояния). Но — только для **редких чтений завершённого состояния**; для continuous-статуса живого прогона индустрия единодушно использует push-стрим уже запущенного процесса, не спавн-на-поллинг.
|
||||
|
||||
## §5. Почему НЕ «публичный read-пакет» из ядра движка
|
||||
|
||||
Репо-факт (проверен арбитражем): `NewReadOnlyRunner` → `openRunner` грузит ПОЛНЫЙ стек — LoadBook/LoadModels/LoadPipeline + Pricer + промпт-шаблоны всех стадий + langpack + repair/terminology-шаблоны (`runner.go:151-266`), потому что честный `Status` обязан ре-рендерить снапшоты волн (drift/rebill: `status.go:492-500` → `rebill.go:95` → snapshot/render), а деноминатор N/M — это $0 ре-ингест источника (`status.go:177-195`). Читальный срез = **12 из 14 internal-пакетов + распил пакета pipeline**. «Тонкой read-библиотеки» не существует; вынос = перенос почти всего ядра из internal. Экосистема подтверждает отсутствие паттерна «библиотека читает живой чужой стор»: bbolt/badger блокируются на локах, prometheus `DBReadOnly` — только для неживых директорий, живые чтения Prometheus отдаёт исключительно через API процесса-владельца.
|
||||
|
||||
## §6. Go-модули: текущая структура правильная; один гард
|
||||
|
||||
- **Два sibling-модуля** (`textmachine/backend` + `textmachine/platform`) — то, что нужно, но по честной причине: НЕ ради видимости (правило `internal` действует по дереву каталогов — один модуль дал бы ту же изоляцию), а ради **изоляции графа зависимостей**: pgx/river платформы не попадают в go.mod/go.sum движка и не двигают его транзитивные версии через MVS — прямой довод от детерминизма прогона.
|
||||
- **Живые эксперименты (go1.26.4):** sibling-модуль при импорте `backend/internal/*` получает ошибку компиляции — шов запечатан toolchain'ом. **Лазейка:** модуль с путём, ВЛОЖЕННЫМ под `textmachine/backend/*`, импортирует internal успешно (проверка — по префиксу import-пути, golang/go#23970). **Ревью-гард: путь Go-модуля платформы/фронта никогда не вкладывать под путь движка.**
|
||||
- **go.work** — коммитить, когда у платформы появится Go-код (паттерн kubernetes/grafana: легитимно для деплойных, не-импортируемых-извне модулей; совет ref/mod «не коммитить» — про публичные библиотеки, не наш случай). go.work решает только dev-удобство: ни видимость, ни версии, ни деплой-скью он не меняет; co-deployed модули синхронизируются git SHA.
|
||||
- Curated-surface-паттерн (k8s staging/apimachinery, gopls в x/tools) — держать в уме, применять ТОЛЬКО если платформе однажды реально понадобится импортировать Go-код движка; пока платформа зовёт движок процессом — версии между модулями не нужны вовсе.
|
||||
|
||||
## §7. Зрелость интеграционной поверхности движка (инвентаризация, арбитраж-подтверждено)
|
||||
|
||||
**Уже готово:** exit-контракт 0 чистый / 2 completed-with-flags / 3 банк-стоп / 1 сбой (`main.go:30-52`) · `status --json` (StatusReport: enum'ы disposition/flag_reason, деньги committed/reserved/ceiling_pct, дрифт, паспорта — `status.go:37-130`) · `export` = детерминированный JSON по умолчанию (+`--pairs`) · NDJSON-логи `LOG_FORMAT=json` на stderr с авто-осями trace_id/book/chapter/chunk/stage/role (`obs/logging.go:15-54`) и живыми событиями (старты волн, `calling model` с оценкой $ ДО вызова, `attempt completed` с ценой, банк-стоп) · 4 класса сайдкаров (mined-signature.yaml · bank-stop.txt · auto-bank.yaml · backups/) · деньги per-book одним запросом из `spend` (`ledger.go:314-322`); per-run — `SUM(request_log.cost_usd) WHERE trace_id` (нижняя граница, телеметрия) либо дельта spend.
|
||||
|
||||
**Малое касание:** событийный эмиттер со стабильным словарём — все границы суть единичные call-sites в pipeline рядом с готовыми slog-вызовами (`bookrun.go:160`, `waverun.go:116/171/131-135`, unit-done = `stagerun.go:296-307`); **событие потолка — единственное, что надо ДОБАВИТЬ** (сейчас деньги только в тексте ошибки, `stagerun.go:472-494`) → **строка 103** · `report --json` (QualityReport уже json-tagged; RequestLogView тегов НЕ имеет — нужна локальная view, поправка арбитража) · JSON-таблица банка (строка 101) · внешний trace-контекст (строка 102) · пофазный прогресс (строка 99, без миграции).
|
||||
|
||||
**Большая работа (и почему не делаем):** публичный read-пакет (§5 — отклонён) · персист манифеста против ре-ингеста (строка 100 — делаем, нужен и самому движку) · глобальный брокер рейт-лимитов (П-2 — единственная по-настоящему новая механика стыка).
|
||||
|
||||
## §8. Ратифицировано (D39.85) — носители
|
||||
|
||||
1. Форма шва = три канала §0; входы дизайна контракта API зафиксированы в строке 95: (i) NDJSON-поток (словарь-enum + version-хендшейк + событие потолка), (ii) артефактный контракт (export-JSON · 101 · 100), (iii) правило ре-синка (идемпотентный апсерт (run_id, seq); `status --json` = канал согласования).
|
||||
2. НОВАЯ строка 103 — событийный эмиттер (малое касание; форма — вместе с контрактом 95).
|
||||
3. Анти-паттерны §0 — запреты; чтение живого SQLite платформой не проектировать и не предлагать.
|
||||
4. Ревью-гард модульных путей (§6); go.work — при появлении кода платформы.
|
||||
5. П-1 платформенного бэклога уточнён формой потока (воркер-обёртка супервайзит процесс, ингестит идемпотентно, SSE из Postgres).
|
||||
|
||||
## §9. Источники (первоисточники)
|
||||
|
||||
bazel.build/remote/bep · developer.hashicorp.com/terraform/internals/machine-readable-ui и /json-format · docs.docker.com/reference/cli/docker/buildx/build · git-scm.com/docs/git-status (porcelain) · kubernetes.io/docs/reference/kubectl/conventions · martinfowler.com/bliki/{CQRS,ReportingDatabase,IntegrationDatabase}.html · microservices.io/patterns/data/{shared-database,transactional-outbox}.html · sqlite.org/{faq#q5,wal,howtocorrupt,uri,rsync,appfileformat,pragma#locking_mode}.html · litestream.io/how-it-works (+/vfs) · fly.io/blog/introducing-litefs · fossil-scm.org/home/doc/trunk/www/fileformat.wiki · go.dev/{ref/mod,doc/go1.4#internalpackages,blog/get-familiar-with-workspaces} · kubernetes.dev/blog/2024/03/19/go-workspaces-in-kubernetes · github.com/kubernetes/kubernetes/blob/master/staging/README.md · github.com/grafana/grafana/blob/main/go.work · pkg.go.dev/{go.etcd.io/bbolt,github.com/dgraph-io/badger/v4,github.com/prometheus/prometheus/tsdb} · живые сборки-эксперименты internal-правил: go1.26.4 linux/amd64 (scratchpad, воспроизводимы по §6).
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
| ID | Хвост | Вес | Источник |
|
||||
|---|---|---|---|
|
||||
| П-1 | **HTTP/SSE-слой и сервисная обвязка** (экс-строка 96 единого бэклога): read-API поверх готовых `OpenReadOnly`-путей движка; SSE-события ПУШИТ воркер, фронт read-model не опрашивает (каждый read-вызов движка — дорогой ре-ингест, до строки 100 единого); аутентификация ратифицирована D39.84: одна серверная сессия в Postgres — `__Host`-кука браузеру · `Authorization: Bearer` десктопу/CLI · principal создаётся ТОЛЬКО в middleware, CSRF только на cookie-пути; ⚠ порядок деплоя: read-путь движка схему НЕ мигрирует (`store.go:135-143`, «schema vN … expects vM») — после апгрейда бинарника по каждой книге первой идёт write-команда | Ф3, после контракта API (строка 95 единого) | D39.81, D39.84, STACK_DECISIONS §5 |
|
||||
| П-1 | **HTTP/SSE-слой и сервисная обвязка** (экс-строка 96 единого бэклога): read-API поверх готовых `OpenReadOnly`-путей движка; SSE-события ПУШИТ воркер, фронт read-model не опрашивает (каждый read-вызов движка — дорогой ре-ингест, до строки 100 единого); аутентификация ратифицирована D39.84: одна серверная сессия в Postgres — `__Host`-кука браузеру · `Authorization: Bearer` десктопу/CLI · principal создаётся ТОЛЬКО в middleware, CSRF только на cookie-пути; ⚠ порядок деплоя: read-путь движка схему НЕ мигрирует (`store.go:135-143`, «schema vN … expects vM») — после апгрейда бинарника по каждой книге первой идёт write-команда; **форма потока ратифицирована D39.85 (`docs/research/23`):** воркер-обёртка платформы супервайзит процесс tmctl, ингестит NDJSON-события идемпотентным апсертом (run_id, seq) в Postgres (Reporting Database, не полный CQRS), SSE — из Postgres; ре-синк на обрыве — `tmctl status --json`; живой SQLite движка НЕ читать (анти-паттерн, аргументы в research/23 §4); ревью-гард: путь Go-модуля платформы никогда не вкладывать под `textmachine/backend/*` | Ф3, после контракта API (строка 95 единого) | D39.81, D39.84, D39.85, STACK_DECISIONS §5 |
|
||||
| П-2 | **Глобальный брокер рейт-лимитов провайдеров** (экс-строка 97): гарды движка per-процесс (`pipeline/ratelimit.go:11`, mistral ~48% отказов под параллелизмом), а лимит провайдера — на ВЕСЬ аккаунт: N прогонов = N независимых гардов против общего лимита; воркер отпрашивается у платформы перед вызовом | ДО второго параллельного пользователя | D39.81, D39.84 |
|
||||
| П-3 | **Очередь и конкурентность по книге**: River на том же Postgres, сериализация по `book_id` + пиннинг книги к одному хосту на MVP, лизы `book_leases` с heartbeat — вежливое ожидание вместо аварии | Ф3-пак платформы | STACK_DECISIONS §5, D39.84 |
|
||||
| П-4 | **Учёт токенов/денег per-user + бюджет-гейт ДО старта задачи** (сырьё уже считает движок: `request_log` + `internal/ledger`; помнить: леджер = нижняя граница — строка 78 единого) | Ф3-пак платформы | platform/README, D39.84 |
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue