Record the cold review of the engine-platform seam: two isolated panels, first run discarded as contaminated, findings and open money gaps

This commit is contained in:
heaven 2026-08-05 05:20:57 +03:00
parent 4d1d9e525c
commit f8d5019c9b
2 changed files with 146 additions and 0 deletions

View file

@ -1,5 +1,11 @@
# 23 — Шов движок ↔ платформа: как индустрия интегрирует batch-движок с control plane
> **→ 05.08: холодное ревью этого шва — `25-seam-cold-review.md`.** Пять изолированных архитекторов,
> не знающих о проекте, получили задачу абстрактно. Несущие решения ниже воспроизведены ими
> независимо (потолок у движка вместо синхронной авторизации · hold→capture · идемпотентность по
> `(run_id, seq)` · запрет читать приватную БД движка · standalone тем же путём кода). Расходится
> ОДНО: они единогласно не делают платформу родителем процесса движка. Ничего не отменяет — читать
> перед правками транспорта (строка 103) и перед П-3.
> **→ 04.08 (D39.99/101): контракт API v0 РАТИФИЦИРОВАН по этому шву** — `../architecture/14-api-contract/`; строка 95 закрыта, хвосты движка = блокеры 99/100/101/103/125; код платформы существует (P0, ждёт приёмки). Упоминания «строки 95» ниже — история.
> **Ревью-шапка (оркестратор №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 не проникают в движок».

View file

@ -0,0 +1,140 @@
# 25 · Холодное ревью шва движок↔платформа: независимые панели
> **Что это.** Шов, ратифицированный D39.85 (`research/23`), вынесен на независимую проверку:
> абстрактная формулировка задачи отдана панелям чистых агентов, ничего не знающих о проекте.
> Вопрос был не «оставить stdout или переехать», а «вот две задачи — как их поженить», без
> подсказки пространства решений. Заказ владельца 05.08: «сформулировать максимально абстрактно и
> посмотреть, какой механизм предложат сами».
>
> **Статус:** фактура. Ничего не ратифицирует и не отменяет; D39.85 в силе. Служит входом для
> решения по строке 103 единого бэклога и по П-1/П-3.
>
> **Кто делал:** оркестратор №14, 05.08.2026. Прогоны — воркфлоу, транскрипты в сессионном
> каталоге (`wf_b2b954de-e83` грязный, `wf_6adb39d7-870` чистый, `wf_2b45042d-115` чекпойнт-плечо).
## ⚠ Первый прогон ЗАГРЯЗНЁН — не цитировать
Восемь агентов первого прогона получили абстрактную формулировку, но остались с файловыми
инструментами в рабочем каталоге: **все восемь** сходили `Bash`/`Read` по репозиторию, узнали
проект и прочитали `research/23`, D-решения, `backend/`, `pricing.go`. Запрет сети трое соблюли;
запрет файлов я поставить забыл — ошибка постановки, не агентов.
Поэтому вывод «независимая панель сошлась на нашей схеме шва» из первого прогона **стоит ноль**:
они её прочитали. Единственное, что от него остаётся, — большинство проголосовало ПРОТИВ
ратифицированного дизайна, а загрязнение толкает к согласию с прочитанным, не против.
Урок для следующих панелей: запрещать **все** инструменты явно и **проверять постфактум** по
`journal.jsonl` (счётчик `tool_use` поагентно). В чистом прогоне проверка дала ноль обращений к
файлам и сети и ноль следов репозитория — только тогда результат брался в работу.
## Метод чистого прогона
Домен переодет (сборка генома, не перевод), числа изменены, ни одного совпадения с репозиторием.
Пять изолированных архитекторов, пять разных линз: холодный архитектор · «отгрузить за три недели
вдвоём» · «проектируй от отказов» · «сначала защити самый развязанный вариант, потом самый
связанный, потом выбери» · «ты это уже строил, и вышло плохо». Схема ответа требовала перечислить
ВСЕ взвешенные механизмы, включая отвергнутые, — census важнее вердикта.
Сценарий: control plane со счетами, кредитами, очередью и HTTP для SPA, деплоится несколько раз в
неделю; воркер обрабатывает одну задачу 430 часов, держит эксклюзивный лок на каталоге, тратит
деньги на платные API, обязан оставаться запускаемым в одиночку на ноутбуке; одна VM, оркестратора
нет.
## Расклад чистой панели
**Родитель-ребёнок с потоком на stdout или унаследованном дескрипторе — 0 выбрали, 5 отвергли.**
Формулировка повторяется почти дословно: *время жизни воркера не должно быть подмножеством времени
жизни control plane*. Названный убийца — `KillMode=control-group`: рестарт control plane убивает
весь его cgroup вместе с 22-часовой задачей. Один: «залатать это `setsid` и двойным форком можно,
но в этот момент ты руками написал systemd, только хуже».
| Механизм | выбрали | второй | отвергли |
|---|---|---|---|
| Процесс-на-задачу под systemd (`Restart=no`, длинный `TimeoutStopSec`, стоп SIGTERM) | **5** | — | 0 |
| Append-only журнал в каталоге задачи, control plane тейлит по курсору | **3** | 1 | 1 |
| Push из воркера в control plane по unix-сокету (HTTP/JSON) | **2** | 2 | 1 |
| Передача конфигурации задачи файлом в каталоге (нет файла = режим ноутбука) | **5** | — | 0 |
| SSE в браузер | **5** | — | 0 |
| Воркер как библиотека внутри control plane | 0 | 0 | **5** |
| Воркер как ребёнок control plane (любой IPC) | 0 | 0 | **5** |
| Чтение stdout/journald как источника событий | 0 | 0 | **5** |
| gRPC · брокер · общая БД · чтение приватной БД воркера · синхронный запрос авторизации на каждый платный вызов | 0 | 0 | **5** каждый |
Отдельно: journald отвергнут как контракт всеми пятью (он **молча роняет** под всплеском и ротирует
по своему расписанию), но всеми пятью оставлен как человеческий канал отладки.
## Что совпало с нами вхолодную
Семь решений панель назвала единогласно, не зная о нас ничего:
| Назвали 5/5 | У нас |
|---|---|
| Предавторизованный потолок, который воркер держит САМ; синхронной авторизации нет | холд + потолок движку (D39.84) |
| Hold → capture → возврат остатка | `Hold`/`Settle`/`Release` |
| Идемпотентное применение по `(job_id, seq)` | `(engine_run_id, seq)` |
| Control plane НИКОГДА не открывает приватную БД воркера | прямой запрет D39.85 §4 |
| Standalone — тот же путь кода, без ветки «есть ли control plane» | так и есть |
| `flock` вместо pid-файлов | так и есть |
| Целочисленные деньги | на платформе да; ⚠ в движке `float64` |
| Запись намерения ДО платного вызова, расчёт после | Р7, есть |
Это, а не вердикт по транспорту, — главный результат: несущие решения шва воспроизводятся
независимо. Транспорт же, по их же словам, меняется при переезде на вторую машину, а перечисленное
переживает без правки.
## Что назвали обязательным, а у нас нет
1. **Реконсилятор мёртвых воркеров** — компонент, не удобство: на старте control plane добрать
хвост журнала, пометить прогон прерванным, вернуть остаток холда. Один добавил: «если оставить
один тест, оставьте SIGKILL реального воркера, иначе этот код сгниёт».
2. **Состояние `uncertain` для окна «упали между платным вызовом и записью расчёта».** Назвали все
пятеро, лечат одинаково: смещение в сторону списания, запись по оценке, флаг человеку,
**никогда молчаливая бесплатная работа**. У нас такого состояния нет.
3. **Сверка с данными провайдера** (3 из 5, ночным прогоном). Совпадает с находкой по коду:
платформа считает целочисленно и точно, но на вход арифметики приходит `float64`, посчитанный по
руками поддерживаемой таблице цен, и у незнакомого слага есть ненулевой дефолтный якорь
(`backend/internal/ledger/pricing.go:27-52`, `CostUSD` на `float64`). Якорь — осознанное решение
с хорошим обоснованием ($0 ослепил бы потолки), но следствие остаётся: при незнакомом слаге сумма
есть оценка. Спорить о ±1 микродолларе поверх числа с процентной систематикой — инвертированный
приоритет.
4. **Пиннинг задачи к версионированному пути бинаря на старте** (4 из 5): иначе апгрейд воркера
меняет поведение между попытками одной задачи. Прямо в цель «детерминизм» (канон §6).
## Чего у них не было и что меняет вес их вывода
Панель отвергла родителя-ребёнка на посылке «редеплой уничтожает часы оплаченной работы». **У нас
эта посылка неверна:** движок чекпойнтится на уровне chunk×stage и резюмится
(`backend/internal/pipeline/resume.go`), поэтому остановка платформы стоит одного чанка в полёте, а
не прогона. Их аргумент про деплой у нас в разы слабее.
Но он не единственный. **Аргумент про вторую машину этим не бьётся** — см. ниже.
## Развилка: одна машина
Все пять независимо назвали ОДИН и тот же переворачивающий факт: **будет ли воркер когда-нибудь
работать на машине, отличной от control plane.** Решение владельца 05.08: **одна машина пока что.**
Растащить при этом можно, и домен этому не мешает: лок и каталог — пер-книжные, а не пер-системные,
поэтому книга пиннится к хосту (это уже записано в `platform/BACKLOG.md`, П-3: сериализация по
`book_id`, пиннинг книги к хосту, лизы `book_leases`). Через границу машины не проезжают ровно три
вещи, и все три сегодня в конструкции есть: платформа как **родитель** процесса; чтение его потока
из пайпа или файла в его каталоге; запуск `tmctl status --json` платформой. Шаг на вторую машину —
это «на каждый хост ставится агент», который спавнит движок, читает поток и толкает события
платформе; идемпотентность, холд, леджер и SSE переживают без правки.
**Разница не в «можно или нельзя», а в цене шага:**
- платформа **родитель** движка → переезд переписывает супервизор: родство процессов, пайп, сигналы
группе, вызов `status --json`, плюс разваливается cgroup-аргумент, которым закрыт PD-13;
- платформа **тейлит журнал по курсору** → переезд подменяет источник, код приёма не меняется.
## Открытое
- Строка 103 единого бэклога: транспорт потока. Ставка на stdout защитима (chunk-резюм делает цену
их сценария низкой), но должна быть записана как СОЗНАТЕЛЬНАЯ, с названной ценой будущего
переезда, а не как «переезд не окупается».
- Три денежные дыры выше (реконсилятор · `uncertain` · сверка с провайдером) — строками единого
бэклога, зона платформы их одна не закроет: половина работы в движке.
- Пиннинг к версионированному бинарю — туда же.
- Чекпойнт-плечо панели (`wf_2b45042d-115`) проверяет, меняет ли дешёвый резюм расклад. Результат
дописать сюда.