Ratify D39.106 seam decision, rewrite research 25 from arm chronology into current form with brief rejection reasons, supersede the transport banner in research 23
This commit is contained in:
parent
39032e5148
commit
481b2ba72b
3 changed files with 86 additions and 106 deletions
|
|
@ -1507,3 +1507,11 @@ API-529-долг закрыт: 8-осевой refute-by-default воркфлоу
|
|||
**2. Док-свип закрытия (норма «актуализация = полный свип»):** 5 сканеров по кластерам (корень · PROGRESS · architecture · research · experiments), ~40 находок применено: передача №13→№14 и счёт очереди скриптом (**109** строк · бэкенд **60** строго / **72** широко · «скоро» **24**) · supersede-указатель доктрины D39.104 в шапку этого файла и баннеры 09-target/10-prompt/06-registry/12-style/research 15·20·24/experiments README·09-pilot · глоссарий +«доктрина инжекта» · норма «без саги» вписана в промт оркестратора · **хроника №9/№10 и закрытые бэкенд-записи срезаны в `archive/PROGRESS-2026-08-01-02.md`** (PROGRESS ~356 → ~230 строк). Отложено до приёмки/лендинга P0 (зона живая, №14): 2 правки `platform/README.md` · правка «кода нет» в `14-api-contract/README` · архивация P0-промта.
|
||||
|
||||
**3. Очередь №14 — CURRENT-STATE (первым — приёмка P0 платформы). Грабли преемнику:** перед запуском любого полигон-скрипта читать код на сетевые вызовы (`--score`/`score_f1` офлайн; голые запуски ПЛАТНЫЕ; `load_dotenv` на импорте — стабить при офлайн-ре-ранах) · перед пере-ранами — sha256-манифест сырья и бэкап харнесса (gold.jsonl пере-собирается байт-в-байт, детерминизм подтверждён) · пинг полигона может врать арифметикой при чистом леджере — сверять из сырья · счёт очереди ТОЛЬКО скриптом · git — только pathspec-форма · чужое незакоммиченное (`platform/*`) не трогать.
|
||||
|
||||
## D39.106 — ШОВ: прогон переживает деплой платформы; журнал в каталоге книги + systemd-юнит-на-прогон; платформа НЕ родитель движка (05.08). ✅
|
||||
|
||||
**1. Решение владельца 05.08.** Верификация до ратификации: четыре чистых панели, ~18 агентов, две модели (research/25, там же отвергнутые альтернативы с причинами) — 0 голосов за «родитель+пайп», 0 за монолит, 0 за gRPC/брокер; атака на готовое решение: «доминирующей альтернативы нет».
|
||||
|
||||
**2. Форма (заменяет ТРАНСПОРТ D39.85 §1; остальное D39.85 в силе):** движок = транзиентный systemd-юнит на прогон (`Restart=no`, SIGTERM); события = `events.jsonl` каталога книги как проекция коммитов SQLite движка (outbox, та же транзакция, что чекпойнт); платформа тейлит, курсор `(engine_run_id, seq)` коммитится с эффектом в одной Postgres-транзакции; `status --json` — ремонт по остановленному прогону. Формат NDJSON/hello/seq, деньги (холд+потолок = защита, события = свежесть, D39.84/D39.100) и запрет на живой SQLite — без изменений.
|
||||
|
||||
**3. Обязательные механизмы при постройке — research/25 §«Обязательные механизмы» (реконсилятор на каждый выход юнита + перезапуск после ребута · intent/`uncertain` + ночная сверка с провайдером · пиннинг версии бинаря на прогон · гейт потолка пер-вызов). Следствия: PD-59 superseded; пайп-путь `supervisor.go` P1 = дев-режим; ответ PD-13 переезжает на cgroup юнита прогона. Строка 103 и денежные строки единого бэклога — при освобождении PROGRESS.md; задачи зонам — промтами.**
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
# 23 — Шов движок ↔ платформа: как индустрия интегрирует batch-движок с control plane
|
||||
|
||||
> **→ 05.08: холодное ревью этого шва — `25-seam-cold-review.md`.** Пять изолированных архитекторов,
|
||||
> не знающих о проекте, получили задачу абстрактно. Несущие решения ниже воспроизведены ими
|
||||
> независимо (потолок у движка вместо синхронной авторизации · hold→capture · идемпотентность по
|
||||
> `(run_id, seq)` · запрет читать приватную БД движка · standalone тем же путём кода). Расходится
|
||||
> ОДНО: они единогласно не делают платформу родителем процесса движка. Ничего не отменяет — читать
|
||||
> перед правками транспорта (строка 103) и перед П-3.
|
||||
> **→ 05.08 (D39.106): ТРАНСПОРТНАЯ часть §1 SUPERSEDED** — по холодному ревью `25-seam-cold-review.md`
|
||||
> (4 панели, ~18 агентов, две модели) и решению владельца «прогон переживает деплой платформы».
|
||||
> Поток идёт НЕ в stdout ребёнку-платформе, а в `events.jsonl` каталога книги (проекция коммитов
|
||||
> SQLite движка); движок — systemd-юнит на прогон; платформа тейлит по курсору `(run_id, seq)`.
|
||||
> Всё остальное этого дока (формат NDJSON/hello/seq · три канала · запрет на живой SQLite · деньги)
|
||||
> подтверждено теми же панелями и В СИЛЕ.
|
||||
> **→ 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 не проникают в движок».
|
||||
|
||||
|
|
|
|||
|
|
@ -1,115 +1,87 @@
|
|||
# 25 · Холодное ревью шва движок↔платформа и топологии сервисов
|
||||
# 25 · Шов движок↔платформа: ратифицированная форма и почему не иначе
|
||||
|
||||
> **Метод.** Задача шва (control plane + многочасовой воркер, деньги, standalone-режим) отдана
|
||||
> панелям изолированных агентов в переодетом домене (сборка генома), без доступа к репозиторию.
|
||||
> Три чистых плеча: базовое (`wf_6adb39d7-870`, 5 агентов) · с добавленным фактом «воркер дёшево
|
||||
> резюмится» (`wf_2b45042d-115`, 5) · топологическое «сколько вообще нужно разворачиваемых единиц»,
|
||||
> без постулата разделения, с вебом (`wf_4ac43daf-ec5`, 6, три модели, включая seat-адвоката
|
||||
> монолита). Изоляция проверена поагентно по журналам: ноль файловых вызовов, ноль следов
|
||||
> репозитория. Первый прогон (`wf_b2b954de-e83`) загрязнён чтением репозитория и ОТБРОШЕН;
|
||||
> правило для будущих панелей: запрещать файловые инструменты явно и проверять постфактум.
|
||||
> **Что это.** Независимая верификация шва и топологии: задача в переодетом домене отдана четырём
|
||||
> чистым панелям (~18 агентов, Opus и Fable, без доступа к репозиторию; изоляция проверена
|
||||
> поагентно по журналам). Финальная тройка: атака на готовое решение + два абстрактных дизайна,
|
||||
> которым явно разрешили REST/gRPC/брокер. Воркфлоу: `wf_6adb39d7-870` · `wf_2b45042d-115` ·
|
||||
> `wf_4ac43daf-ec5` · `wf_b7eae996-b9c` (первый прогон `wf_b2b954de-e83` загрязнён чтением
|
||||
> репозитория и отброшен; правило: файловые инструменты панелям запрещать явно, проверять
|
||||
> постфактум).
|
||||
>
|
||||
> **Статус:** фактура. D39.85 в силе; служит входом для строки 103 единого бэклога, П-1/П-3 и
|
||||
> решения по транспорту. Оркестратор №14, 05.08.2026.
|
||||
> **Статус:** решение владельца 05.08 «прогон переживает деплой платформы» ратифицировано
|
||||
> **D39.106**. Оркестратор №14.
|
||||
|
||||
## Вердикты панелей
|
||||
## Форма
|
||||
|
||||
| Вопрос | Результат |
|
||||
|---|---|
|
||||
| Сколько разворачиваемых единиц | **2 — единогласно (6/6)**, включая seat-адвоката монолита; за монолит 0 |
|
||||
| Платформа — родитель процесса движка + поток на stdout/fd | **0 выбрали, 10 отвергли** (оба плеча) |
|
||||
| Дешёвый резюм воркера меняет расклад? | **Нет.** 5/5: «факт снял ограничения, но выбор не сделал»; при дорогом прерывании ушли бы в ту же сторону сильнее |
|
||||
| Канал воркер→платформа | Append-only журнал в каталоге прогона, тейл по курсору — 8/10; unix-сокет-push упал в обоих плечах по недолговечности через рестарты платформы |
|
||||
| Супервизор воркера | systemd, юнит-на-задачу, `Restart=no`, стоп SIGTERM — 10/10 |
|
||||
| gRPC · брокер · общая БД · чтение приватной БД воркера · stdout/journald как источник событий · синхронная авторизация на каждый платный вызов | отвергнуты почти единогласно (чтение приватной БД: 1 голос «за» из 11 — единственное расхождение с D39.85 §4 за все плечи) |
|
||||
- **Два Go-модуля** (движок · платформа), граница — модульная, D39.85-гард в силе.
|
||||
- Движок — **транзиентный/шаблонный systemd-юнит на прогон** (`Restart=no`, стоп SIGTERM, щедрый
|
||||
`TimeoutStopSec`), платформа его **не родитель**: её деплой прогоны не трогает.
|
||||
- События — **`events.jsonl` в каталоге книги**, append-only, как **проекция** уже закоммиченных
|
||||
строк SQLite движка (та же транзакция, что чекпойнт chunk×stage; outbox, не вторая запись).
|
||||
- Платформа **тейлит** журнал; запись курсора — `(engine_run_id, seq)`, байтовое смещение — хинт;
|
||||
курсор коммитится в той же Postgres-транзакции, что эффект события.
|
||||
- Деньги: **холд + потолок движку = защита** (не зависит от живости платформы), события — свежесть;
|
||||
hold→capture→release. `tmctl status --json` — ремонт, только по остановленному прогону (лок).
|
||||
- **Реконсилятор** — обязательный компонент: на каждый выход юнита (D-Bus-сигнал +
|
||||
`ExecStopPost=`-маркер) и на буте, где он ПЕРЕЗАПУСКАЕТ прерванные прогоны (транзиентные юниты
|
||||
не переживают ребут VM).
|
||||
- Формат потока (NDJSON · hello · версия · seq), D39.84, запрет читать живой SQLite движка — без
|
||||
изменений; журнал им не является.
|
||||
|
||||
## Подтверждено вхолодную (совпало с нашими решениями)
|
||||
## Отвергнутые альтернативы (кем и почему — кратко)
|
||||
|
||||
Названо 5/5 агентами, не знавшими о проекте: предавторизованный потолок, который воркер держит сам
|
||||
(= холд + потолок движку, D39.84) · hold→capture→возврат остатка (`Hold`/`Settle`/`Release`) ·
|
||||
идемпотентное применение по `(run_id, seq)` · запрет читать приватную БД воркера (D39.85 §4) ·
|
||||
standalone тем же путём кода · `flock` вместо pid-файлов · запись намерения ДО платного вызова (Р7)
|
||||
· целочисленные деньги (⚠ у нас — на платформе; в движок float64 входит раньше, см. «Деньги»).
|
||||
| Вариант | Счёт | Причина отказа |
|
||||
|---|---|---|
|
||||
| Монолит (движок внутри платформы) | 0 из ~18 | Радиус поражения (леджер+вход в одном адресном пространстве с часто меняющимся движком); «одна транзакция решает деньги» ложна — БД прогона + леджер + провайдер = три хранилища при любом числе процессов. Пороги пересмотра: без cgo ✅ · деплой реже месяца ИЛИ p99 ≤ 2 ч ❌ · кросс-версионный резюм в CI ❌ |
|
||||
| Платформа — родитель + пайп (stdout/fd) | 0 из 15 | Время жизни движка — подмножество платформы: деплой/рестарт убивает или осиротляет прогон; дешёвый резюм это НЕ лечит (проверено отдельным плечом: факт «прерывание стоит минуты» не сдвинул ни один голос) |
|
||||
| gRPC/HTTP-сервис поверх движка | 0, отвергли явно | Изобретает жизненный цикл, которого у движка нет (одна задача, эксклюзивный лок); поток всё равно умирает на рестарте платформы — долговечный буфер нужен, а он и есть журнал |
|
||||
| Push в платформу (unix-сокет/localhost HTTP) | 2 голоса в одном плече, затем 0 | Платформа лежит несколько раз в неделю ПО ЗАМЫСЛУ ⇒ push требует буфер+ретраи+дедуп = журнал; плюс неаутентифицированный локальный писатель в денежные таблицы |
|
||||
| Брокер (NATS/Redis/Kafka) | 0 | Третий stateful-демон на одной VM для 1 продюсера и 1 консюмера; redelivery сам создаёт риск двойного списания |
|
||||
| Чтение чужой БД (в обе стороны) | 1 голос из ~18 | Сварка с приватной схемой, меняющейся еженедельно; лок-конфликт с живым писателем |
|
||||
| Выделенный fd 3 вместо stdout | 0 | Никому не понадобился при свободном выборе; отдельно замерено: fd 1/2 дают SIGPIPE-смерть сироты, fd 3 — нет |
|
||||
| journald/stdout как источник событий | 0 | journald молча роняет под всплеском и ротирует сам; людям — да, контракту — нет |
|
||||
|
||||
**Два модуля подтверждены, но несущие доводы другие, чем в наших доках.** Всё «процессное»
|
||||
(пиннинг версии, cgroups, независимый рестарт, пер-задачные журналы) даёт и один бинарь,
|
||||
exec-ающий сам себя транзиентным юнитом. До «двух ПОСТАВОК» доживают ровно два довода:
|
||||
**граница как ошибка компиляции** (модульный гард D39.85 — ровно это) и **независимые релизные
|
||||
поезда** (фикс движка не ждёт CI платформы). Лучшая карта монолита — «одна транзакция решает
|
||||
деньги» — фальшива: приватная БД задачи + глобальный леджер + провайдер = **три хранилища при
|
||||
любом числе процессов**, outbox и сверка нужны в любом исходе. Пороги, при которых монолит стал бы
|
||||
прав (все разом): движок без cgo ✅ · деплой реже месяца ИЛИ p99 задачи ≤ 2 ч ❌ · кросс-версионный
|
||||
резюм в CI ❌ · команда ≤ 3 с одним владельцем обеих половин — сегодня не проходим, это и есть
|
||||
триггер пересмотра.
|
||||
## Подтверждено вхолодную (наши решения воспроизведены независимо)
|
||||
|
||||
## Настоящие драйверы против «платформа — родитель» (не зависят от стоимости прерывания)
|
||||
Потолок у движка вместо синхронной авторизации · hold→capture→release · идемпотентность
|
||||
`(run_id, seq)` · запрет читать приватную БД движка · standalone тем же путём кода (не режимом) ·
|
||||
`flock` вместо pid-файлов · запись намерения до платного вызова (Р7) · целочисленные деньги ·
|
||||
SSE, не WebSocket · `status` как ремонт, не поллинг.
|
||||
|
||||
1. **Несовпадение времён жизни:** деплой платформы несколько раз в неделю против прогона 4–30 ч.
|
||||
2. **Плановая недоступность платформы делает долговечность требованием к каналу:** любой push-канал
|
||||
требует буфера/ретраев/дедупа — а этот буфер и есть журнал.
|
||||
3. **Атомарность денег:** «юнит сделан» и «юнит стоил $X» — одна транзакция в хранилище воркера.
|
||||
4. **Радиус поражения:** леджер и аутентификация не делят адресное пространство и OOM-цель с
|
||||
часто меняющимся воркером.
|
||||
5. **Standalone не должен быть режимом:** непрогоняемая ветка `--no-server` гниёт.
|
||||
Атака на готовое решение: вердикт **build-with-changes**, доминирующая альтернатива — **none**.
|
||||
|
||||
**Наше смягчение, которого у панелей не было:** при остановке платформы движок останавливается
|
||||
тоже (сигнал группе; на stdout — SIGPIPE), окна «пишет без слушателя» нет, события не теряются.
|
||||
Довод №2 бьёт по нам не потерей событий, а тем, что **каждый деплой платформы прерывает все идущие
|
||||
переводы** ценой одного чанка каждый.
|
||||
## Обязательные механизмы при постройке (носители в D39.106)
|
||||
|
||||
## Развилка владельцу
|
||||
**Деньги:**
|
||||
- write-ahead intent + состояние **`uncertain`** для окна «платный вызов прошёл, коммит не успел»:
|
||||
смещение к списанию, запись по оценке, флаг человеку; молчаливой бесплатной работы не бывает;
|
||||
- **ночная сверка с данными провайдера** — приоритетнее споров об округлении: в движок суммы
|
||||
входят float64 по ручной таблице цен с ненулевым якорем незнакомого слага
|
||||
(`backend/internal/ledger/pricing.go:27-52`), систематика процентная;
|
||||
- **гейт потолка на каждый платный вызов**, не на юнит работы;
|
||||
- state-machine строки прогона в Postgres (холд может утечь до создания каталога); поднятие
|
||||
потолка — файлом через atomic rename на границах юнитов; кандидат: холд траншами (~часы
|
||||
ожидаемого расхода), не на весь прогон.
|
||||
|
||||
**Должен ли идущий перевод переживать деплой платформы?**
|
||||
- **Да** → журнал в каталоге прогона + движок отдельным юнитом; платформа перестаёт быть родителем.
|
||||
- **Нет** → нынешняя форма (родитель + stdout) законна, но фиксируется как сознательная ставка с
|
||||
явной ценой; формулировка «переезд не окупается» опровергнута.
|
||||
**Эмиттер журнала (строка 103):**
|
||||
- `events` (fsync, seq, деньги) ≠ `status` (atomic rename, косметика, не для биллинга);
|
||||
- на старте движок ftruncate'ит хвост до последнего целого `\n` и дописывает с last-seq+1 —
|
||||
побайтовая идентичность переэмиссии не требуется;
|
||||
- **пиннинг прогона к версионированному пути бинаря**; резюм другой версией — только по явному
|
||||
флагу (движок выкатывается чаще, чем идёт прогон);
|
||||
- сброс буфера на всех путях выхода (`os.Exit` пропускает defer — теряются финальные события);
|
||||
- живость = растущий `seq`, не удерживаемый лок; политика обратного давления выбирается явно.
|
||||
|
||||
Формат событий (NDJSON, hello-хендшейк, `(run_id, seq)`) не меняется ни в одном исходе — меняется
|
||||
только кто спавнит и откуда читает. Решение владельца 05.08: одна машина пока что; при второй
|
||||
машине умирают файловая шина/сокет/`systemctl start`, переживают outbox, денежная модель,
|
||||
идемпотентность, SSE. Цена переезда мала, если платформа тейлит журнал, и велика, если она родитель.
|
||||
**Опс:** privilege-модель создания юнитов (linger-пользователь либо узкое polkit-правило) решить до
|
||||
постройки · состояние systemd — не истина (источник на буте = каталоги + Postgres) · диск ограничить
|
||||
(`ENOSPC` на ФС леджера ломает денежные инварианты; квота на прогон, проверка места при допуске) ·
|
||||
ключи идемпотентности провайдеров протухают — replay это оптимизация, не гарантия · отдача SPA из
|
||||
бинаря платформы (один origin) — вопрос к П-1, не решено.
|
||||
|
||||
## Деньги: обязательные механизмы, которых у нас нет
|
||||
## Открытое
|
||||
|
||||
1. **Реконсилятор мёртвых воркеров** (компонент, не удобство): на старте платформы добрать хвост,
|
||||
пометить прогон прерванным, вернуть остаток холда. Тестировать SIGKILL'ом реального воркера.
|
||||
2. **Состояние `uncertain`** для окна «упали между платным вызовом и записью расчёта»: смещение в
|
||||
сторону списания, запись по оценке, флаг человеку; молчаливой бесплатной работы не бывает.
|
||||
3. **Сверка с данными/инвойсом провайдера** (ночной прогон). Приоритетнее споров об округлении:
|
||||
в движке суммы считаются в float64 по ручной таблице цен с ненулевым дефолтным якорем для
|
||||
незнакомого слага (`backend/internal/ledger/pricing.go:27-52`) — систематика процентная, а не
|
||||
микродолларовая.
|
||||
4. **Гейт на каждый платный вызов, а не на юнит работы** — «перерасход ≤ один юнит» без этого
|
||||
утверждение, а не механизм. (Наши вызовы синхронные — оценка перерасхода держится, но гейт
|
||||
всё равно пер-вызовный.)
|
||||
|
||||
## Проектные требования к эмиттеру потока (задать до постройки, строка 103)
|
||||
|
||||
- **Outbox правильно:** строка расхода — в БД задачи, в той же транзакции, что чекпойнт;
|
||||
`events.jsonl` ПРОИЗВОДИТСЯ из неё релеем (иначе двойная запись — ловушка, в которую попали
|
||||
3 из 6 агентов, призывая сам паттерн).
|
||||
- **`events` ≠ `status`:** долговечный денежный журнал (fsync, seq) отдельно от косметического
|
||||
снапшота живости (atomic rename, ~2 с, из него никогда не биллить).
|
||||
- **Перекос версий:** прогон может резюмить более новый движок, чем начал. Штамповать версию в
|
||||
каталоге, резюм чужой версией — только по явному флагу. (В канон детерминизма, рядом с
|
||||
`brief_hash`.)
|
||||
- **Живость = растущий `seq`**, а не удерживаемый лок.
|
||||
- **Сброс буфера на всех путях выхода:** `os.Exit`/`log.Fatal` пропускают defer и теряют финальные
|
||||
события.
|
||||
- **Обратное давление:** пайп/канал ограничен; политика на переполнение (блокировать воркер или
|
||||
ронять с маркером `events_dropped`) выбирается явно, молчаливой третьей нет.
|
||||
|
||||
## Прочее найденное
|
||||
|
||||
- **Диск не ограничен нигде:** `ENOSPC` на ФС леджера ломает денежные инварианты разом. Леджер на
|
||||
своей ФС, квота на задачу, проверка места при допуске. (В зону платформы при деплой-слое.)
|
||||
- **Состояние systemd — не истина:** транзиентный юнит выгружается, VM перезагружается; источник
|
||||
истины на старте = каталоги задач + БД платформы.
|
||||
- **Ключи идемпотентности провайдеров протухают** (порядок суток) — replay как оптимизация ретрая,
|
||||
не гарантия.
|
||||
- **SPA из бинаря платформы** (один origin, нет перекоса версий) — 6/6 у панели; у нас не решено,
|
||||
вопрос к П-1/фронту.
|
||||
- SIGPIPE зависит от номера дескриптора (fd 1/2 — смерть процесса, прочие — EPIPE; замерено):
|
||||
нынешний stdout даёт бесплатную остановку осиротевшего движка, переезд на fd 3 её ломает.
|
||||
- Защита stdout от посторонних записей, если канал остаётся: dup-перехват на уровне дескриптора в
|
||||
`main` движка, только в области потоковой команды (безусловный сломал бы `tmctl status --json`).
|
||||
- Строка 103 единого бэклога и денежные строки — завести при освобождении `docs/PROGRESS.md` от
|
||||
чужих незакоммиченных правок; задачи зонам — отдельными промтами.
|
||||
- Вторая машина (решение владельца: одна пока что): при переезде умирают файловая шина и
|
||||
`systemctl start` как глагол — заменяются хост-агентом, шлющим те же строки; outbox, деньги,
|
||||
идемпотентность, SSE переживают без правки. Пиннинг книги к хосту уже в П-3.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue