From f8d5019c9b9972c7892f3ae14300e29abe9d0049 Mon Sep 17 00:00:00 2001 From: heaven Date: Wed, 5 Aug 2026 05:20:57 +0300 Subject: [PATCH] Record the cold review of the engine-platform seam: two isolated panels, first run discarded as contaminated, findings and open money gaps --- docs/research/23-engine-platform-seam.md | 6 + docs/research/25-seam-cold-review.md | 140 +++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 docs/research/25-seam-cold-review.md diff --git a/docs/research/23-engine-platform-seam.md b/docs/research/23-engine-platform-seam.md index 574b0db0..09407379 100644 --- a/docs/research/23-engine-platform-seam.md +++ b/docs/research/23-engine-platform-seam.md @@ -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 не проникают в движок». diff --git a/docs/research/25-seam-cold-review.md b/docs/research/25-seam-cold-review.md new file mode 100644 index 00000000..424caaad --- /dev/null +++ b/docs/research/25-seam-cold-review.md @@ -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, деплоится несколько раз в +неделю; воркер обрабатывает одну задачу 4–30 часов, держит эксклюзивный лок на каталоге, тратит +деньги на платные 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`) проверяет, меняет ли дешёвый резюм расклад. Результат + дописать сюда.