From 07a8d9751757b23b3a77bb5772659e2ccc9f12a4 Mon Sep 17 00:00:00 2001 From: "Claude (backend session)" Date: Sun, 2 Aug 2026 03:38:35 +0300 Subject: [PATCH] Ratify D39.85: industry-standard engine-platform seam recorded in research 23, event stream plus materialization plus artifacts, anti-patterns banned, emitter row opened --- docs/PROGRESS.md | 11 +++- docs/README.md | 2 +- docs/architecture/05-decisions-log.md | 6 +- docs/research/23-engine-platform-seam.md | 77 ++++++++++++++++++++++++ platform/BACKLOG.md | 2 +- 5 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 docs/research/23-engine-platform-seam.md diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index decb582a..960631f9 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -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 велит фронт-сессии коммитить самой — против «сессии не коммитят»; рекомендация — право коммита фронту В СВОЕЙ зоне, пост-хок ревью; до слова владельца действует старая норма. diff --git a/docs/README.md b/docs/README.md index c5f5ccc5..3b96b8bd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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). diff --git a/docs/architecture/05-decisions-log.md b/docs/architecture/05-decisions-log.md index 413a2164..49362670 100644 --- a/docs/architecture/05-decisions-log.md +++ b/docs/architecture/05-decisions-log.md @@ -1,4 +1,4 @@ -# Журнал решений оркестратора — контракт D1–D39.84 (развязки 04.07 · пакеты 09–10.07 · приёмка/качество-первым/пивот/эмпирика 11–12.07 · арх-ресет+стройка пере-прогонного стека 13–19.07) +# Журнал решений оркестратора — контракт D1–D39.85 (развязки 04.07 · пакеты 09–10.07 · приёмка/качество-первым/пивот/эмпирика 11–12.07 · арх-ресет+стройка пере-прогонного стека 13–19.07) > **⟶ КАРТА АКТУАЛЬНОСТИ (ревизия D31, продлена до D38.2 [12.07]; исторические записи ниже НЕ переписываются — дисциплина D23.3).** Читая контракт целиком, держи под рукой, что чем перекрыто: > ⚠ **Навигация (актуализация 01.08):** карта ниже детально покрывает D1–D39.28; решения D39.29+ живут хронологически в теле файла, **свежая голова — С ХВОСТА** (новые ноты аппендятся вниз). Сводка текущей головы и очередь — CURRENT-STATE в `../PROGRESS.md`. @@ -1255,3 +1255,7 @@ API-529-долг закрыт: 8-осевой refute-by-default воркфлоу ## D39.84 — Стек фронта/платформы запинен, восемь решений владельца ратифицированы, работы движка под фронт получили строки, перевод не индексируется (02.08) По релею отчёта фронт-сессии (исследование стека закрыто: 15 агентов — 7 направлений × независимые скептики-опровергатели + синтез, версии сверены с вебом 02.08, blocker/major-поправки скептиков записаны исправленными вариантами; артефакты `frontend/docs/STACK_DECISIONS.md` и переписанный `frontend/docs/FRONTEND_SESSION_PROMPT.md` — фронт режется на этапы S0–S7, первая кодовая сессия кончается витриной токенов). Приёмка оркестратора: 10-агентный refute-воркфлоу — ВСЕ несущие бэкенд-утверждения записки CONFIRMED по коду, 8/8 веб-пинов подтверждены первоисточниками (TS7 GA 08.07.2026 БЕЗ программного API до 7.1 → TS 6.0.3 · Vite 8 = Rolldown · Fleet закрыт 22.12.2025 · react-resizable-panels v4 Group/Separator · react-router 8.3 Node≥22.22 — стенд 22.22.3 проходит). **(1) Восемь решений владельца:** рынок глобальный, SEO только Google (Яндекс снят) · выравнивание колонок грубое по единицам экспорта — механизм ГОТОВ (`tmctl export --pairs`, побайтное выравнивание по edit-unit, `internal/pipeline/export.go:89-93`; уточнение приёмки: draft-only-пайплайн шипит по чанкам, для штатного edit-пайплайна утверждение точно) · подсветка сомнительных мест только блок/глава, внутритекстовые спаны НЕ строятся (у $0-гейтов нет байтовых смещений) · остановка на подписи банка = параметр запуска per-run — ГОТОВО (`--verify-bank`, exit 3, `invocation.go:108`/`main.go:39-46`; галочка формы = передача флага, движок не трогается; в draft-only не останавливает — `mining.go:193-197`) · оплат в MVP нет, денежных полей в UI нет вовсе (расход остаётся телеметрией владельца; потолки/rebill-согласие движка канон §5 не трогает) · десктоп = устанавливаемое PWA первым шагом, Tauri по явным триггерам · макет-инструменты не используются, источник вида = пиксель-замеры референса (Fleet невоспроизводим — растровое совпадение не критерий готовности) · аутентификация: ОДНА серверная сессия, предъявление кукой (браузер) или Bearer (десктоп/CLI), principal только в middleware — вход дизайна П-1 платформенного бэклога. **(2) ПТ-34, продуктовый запрет:** ни один символ пользовательского перевода никогда не попадает на публично индексируемый URL — ни витрин, ни «поделиться», ни публичных ссылок чтения (авторское право + политика Google по масштабируемому машин-контенту бьют по всему домену); носитель — двухконтурный деплой (SPA `app.<домен>` целиком под noindex + статический публичный контур, STACK_DECISIONS §4). **(3) ПТ-21 переформулировано:** якорь чтения = edit-unit экспорта, НЕ «чанк пайплайна» — финальный текст существует только на уровне юнита (~1.9 юнита/главу по замеру фронта; «4276 юнитов» из артефактов репо не выводится — полнокнижной БД не существует, что само подтверждает тезис о ре-ингесте; принято как замер сессии, порядок величин сходится с coldrun-a); читалка обязана жить с «глава = один блок». **(4) Работы движка → строки 99–102** (каждая проверена кодом): 99 пофазный прогресс draft N/M ∥ edit N/M (done = все draft-строки членов + edit-строка, `status.go:328-331/365` — индикатор 0% ВСЮ черновую волну; `chunk_status` уже несёт гранулярность — выводимо без миграции схемы) · 100 персист манифеста глав/чанков + хеш источника (каждый read-вызов заново ингестит и режет исходник — `status.go:177-195`, redrive дважды за вызов; замер фронта 1.42–1.51 с CPU/23 МБ; `chunker_version` уже в снапшоте — `pipeline/render.go:53`) · 101 машиночитаемая таблица подписи банка (кап 20 = только stdout `render.go:98`; полная таблица — текстовый сайдкар `.bank-stop.txt`; JSON нет нигде) · 102 внешний trace-контекст в tmctl (TraceID минтится заново каждым вызовом — `main.go:67-70`; внутри вызова трасса цельная, `request_log.trace_id` персистится). **(5) Противоречие разрешено ВЕРНО:** рекомендация интеграционного агента «долгоживущий демон в backend» отвергнута самой сессией по D39.81 — ратификация удержана, HTTP/SSE/очередь в `platform/`. **(6) Две ловушки эксплуатации подтверждены и заведены входами дизайна (строка 95 · П-1):** подпись термина ≠ UPDATE строки — банк пересобирается каждым прогоном (seedGlossary REPLACES + ReplaceBank DELETE/re-INSERT, `seeding.go:18/110`, `glossary.go:158-162`; контракт подписи пишет в файлы-источники: mined-delta/сид; след затирания — только журнальная строка glossary_revisions, без предупреждения) · порядок деплоя платформы: read-путь схему НЕ мигрирует никогда (`store.go:135-143`, «schema vN … expects vM» в обе стороны; чинит write-команда, годится и `redrive --dry-run`; `tmctl backup` НЕ мигрирует). **(7) Норма зонных бэклогов (решение владельца при лендинге):** фронт и платформа держат СВОИ бэклоги в своих зонах — `frontend/docs/BACKLOG.md` (Ф-1..Ф-6) · `platform/BACKLOG.md` (П-1..П-4); засеяны оркестратором при лендинге переносом уже записанных отложенных решений, дальше ведут зоны; ЕДИНЫЙ бэклог PROGRESS фронт/платформа-строк НЕ принимает — остаётся трекером движка/полигона/доков; движковые стыки фронта (зона «бэкенд») живут в едином (строки 99–102); строки 96/97 переехали в платформенный (П-1/П-2) с ID-надгробиями-указателями (ID стабильны навсегда — внешние ссылки не рвутся). **(8) Лендинг зон** `frontend/` + `platform/` + `.gitignore` (`frontend/references/` — скриншоты вне git, пины замеров сохранены в доках). ⚠ Норма-вопрос ВЛАДЕЛЬЦУ: промт S0 велит фронт-сессии «заленди отдельным коммитом» — против нормы «сессии не коммитят» (ORCHESTRATOR_SESSION_PROMPT §роль; прецедент исключения есть — пре-рег фризы полигона); рекомендация оркестратора: дать фронт-сессиям право коммита В СВОЕЙ зоне (изолирована от движка/голдена; шесть сессий визуальных итераций через релей-приёмку каждой = трение без выигрыша), пост-хок ревью оркестратора; до слова владельца действует старая норма, промт фронта не правился (чужая зона) (02.08.2026, оркестратор №9). ✅ + +## D39.85 — Шов движок↔платформа: индустриальный стандарт ратифицирован — событийный поток, материализация, артефакты; ресёрч записан (02.08) + +По слову владельца («двигаемся в сторону стандарта индустрии, запиши что узнал аргументированно»). Ресёрч: `docs/research/23-engine-platform-seam.md` (5-агентный воркфлоу: три веб-направления по первоисточникам + репо-инвентаризация + независимый арбитраж — 9/9 вердиктов подтверждены, 3 поправки внесены; правила internal проверены ЖИВЫМИ сборками go1.26.4; $0). Ратифицировано: **(1) Форма шва — три канала:** живой статус = версионированный однонаправленный NDJSON-поток событий движка (шаблон terraform -json: version-хендшейк первой строкой, semver «минор = ignore-unknown / мажор = reject», stdout только потоку — человеческие логи уже на stderr) → воркер-обёртка платформы материализует в СВОЙ Postgres идемпотентным апсертом (run_id, seq) → SSE и все чтения ТОЛЬКО из Postgres (Reporting Database, НЕ полный CQRS — предостережение Фаулера принято); массивный контент = артефакты границ стадий (export-JSON готов · таблица банка = строка 101 · манифест = строка 100); пост-фактум/ре-синк = `status --json`/`export` (CLI-as-API по критериям git-porcelain/kubectl/terraform — только для завершённого состояния, не для поллинга живого прогона). Реконсиляция обязательна: на обрыве потока материализация пересобирается из status --json и артефактов (BEP-каveat о потерянных при краше событиях). **(2) Анти-паттерны — запреты:** платформа НЕ читает живой SQLite движка (EXCLUSIVE-локи + смешение локинг-протоколов = порча по howtocorrupt §2.4 · checkpoint starvation = операционное проникновение · IntegrationDatabase-связность со схемой v1–v14+ · индустрия единодушна: Litestream/LiteFS/sqlite3_rsync/Fossil строят снапшот/реплику, не шарят живой файл); движок НЕ пушит HTTP (направление: движок эмитит — адаптер платформы пушит); человеческий вывод не парсится; неверсионированный ad-hoc JSON не эмитится (прецедент гниения docker jsonmessage); допустимая будущая форма чтения файла — ТОЛЬКО immutable-снапшот, экспортированный самим движком (VACUUM INTO уже есть — F4) с явным export-контрактом. **(3) Публичный read-пакет ОТКЛОНЁН репо-фактом:** NewReadOnlyRunner тянет 12 из 14 internal-пакетов (полный конфиг-стек + шаблоны + langpack + ре-рендер снапшотов, `runner.go:151-266`; арбитраж усилил: 12/14, не 10/13) — тонкого среза не существует; процессный контракт D39.81 подтверждён дословно. **(4) Модули:** sibling-структура двух модулей правильная — честный аргумент НЕ видимость (один модуль дал бы ту же изоляцию internal), а изоляция графа зависимостей: pgx/river не попадают в go.mod движка и не двигают транзитивные версии через MVS — довод от детерминизма; живой эксперимент-лазейка: модуль с путём, вложенным под `textmachine/backend/*`, импортирует internal (проверка по префиксу пути, golang/go#23970) → **ревью-гард: пути Go-модулей платформы/фронта никогда не вкладывать под путь движка**; go.work закоммитить при появлении Go-кода платформы (паттерн kubernetes/grafana). **(5) Носители:** НОВАЯ строка 103 (NDJSON-эмиттер со стабильным словарём + событие потолка — единственное отсутствующее, деньги сейчас только в тексте ошибки `stagerun.go:472-494`; малое касание — единичные call-sites рядом с готовыми slog) · входы контракта 95 дополнены тройкой (i)поток/(ii)артефакты/(iii)ре-синк · П-1 платформенного бэклога уточнён формой потока · README research-навигация += 23. Дизайн-промт контракта 95 — по слову владельца; словарь событий и продуктовый словарь статусов (ПТ-33) проектируются вместе (02.08.2026, оркестратор №9). ✅ diff --git a/docs/research/23-engine-platform-seam.md b/docs/research/23-engine-platform-seam.md new file mode 100644 index 00000000..cbe40278 --- /dev/null +++ b/docs/research/23-engine-platform-seam.md @@ -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). diff --git a/platform/BACKLOG.md b/platform/BACKLOG.md index 638573a6..df4706ec 100644 --- a/platform/BACKLOG.md +++ b/platform/BACKLOG.md @@ -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 |