Add the checkpoint arm to the cold seam review: the cheap-resume counter-argument did not survive an independent panel given that exact fact

This commit is contained in:
heaven 2026-08-05 05:29:40 +03:00
parent 6ac26f38ae
commit d60cf31d97

View file

@ -100,14 +100,79 @@
4. **Пиннинг задачи к версионированному пути бинаря на старте** (4 из 5): иначе апгрейд воркера
меняет поведение между попытками одной задачи. Прямо в цель «детерминизм» (канон §6).
## Чего у них не было и что меняет вес их вывода
## Чего у них не было — и естественный эксперимент на этом (плечо 2)
Панель отвергла родителя-ребёнка на посылке «редеплой уничтожает часы оплаченной работы». **У нас
эта посылка неверна:** движок чекпойнтится на уровне chunk×stage и резюмится
(`backend/internal/pipeline/resume.go`), поэтому остановка платформы стоит одного чанка в полёте, а
не прогона. Их аргумент про деплой у нас в разы слабее.
Панель отвергла родителя-ребёнка на посылке «редеплой уничтожает часы оплаченной работы». У нас эта
посылка слабее: движок чекпойнтится на уровне chunk×stage и резюмится
(`backend/internal/pipeline/resume.go`), поэтому остановка платформы стоит одного чанка, а не
прогона. Оркестратор выдвинул это как контраргумент — и проверил его чужими руками, а не оставил
своим мнением.
Но он не единственный. **Аргумент про вторую машину этим не бьётся** — см. ниже.
**Плечо 2 (`wf_2b45042d-115`, тоже чистое — проверено поагентно):** та же формулировка, тот же
переодетый домен, та же изоляция, **изменена ровно одна переменная** — в сценарий добавлено, что
воркер чекпойнтится ежеминутно и резюмится, а прерывание стоит минут и центов, никогда не часов.
**Расклад не сдвинулся.**
| | плечо 1 (без факта) | плечо 2 (с фактом) |
|---|---|---|
| Родитель-ребёнок + поток на stdout/дескрипторе | 0 выбрали, 5 отвергли | **0 выбрали, 5 отвергли** |
| systemd, юнит-на-задачу | 5 | **5** |
| Append-only журнал с курсором | 3 (+1 вариантом) | **5** (4 файлом, 1 таблицей внутри БД воркера) |
| Push по unix-сокету | 2 (+2 вторым) | **0** — упал по причине, к новому факту не относящейся |
Поле «сколько весил дешёвый резюм» отвечали все пятеро, и все пятеро сказали одно: **факт снял
ограничения, но выбор не сделал.** «Весил много, но почти целиком в сторону, обратную очевидной» ·
«повлиял на жизненный цикл и почти не повлиял на механизм» · «изменил мою уверенность и число
строк куда сильнее, чем выбор». Один прямо опознал постановку: *«свойство вставлено в бриф как
приманка к выводу „прерывания дешёвы, значит просто слинкуйте воркер в control plane“»*.
Контрфактическую сторону тоже проверили: при ДОРОГОМ прерывании они ушли бы в ту же сторону
сильнее, а не назад. То есть переменная не является шарниром ни в одну сторону.
**Что она реально изменила** — объём механизма при той же форме: не нужен протокол drain/quiesce,
не нужно рукопожатие «можно ли тебя останавливать», не нужен интерлок деплоя, `systemctl stop`
принимается как полный глагол остановки. Один оценил: «примерно половина механизма, который иначе
понадобился бы, этим свойством удаляется». И ещё: факт **сменил основание отказа** от варианта
«воркер внутри control plane» — аргумент «деплой убьёт задачу» перестал работать, и отказ переехал
на радиус поражения (леджер и аутентификация не должны делить адресное пространство и OOM-цель с
еженедельно меняющимся воркером). Вердикт тот же, обоснование другое.
**Вывод: контраргумент про chunk-резюм не работает.** Настоящие драйверы, названные независимо от
стоимости прерывания:
1. **Несовпадение времён жизни** — деплой несколько раз в неделю против задачи на 430 часов.
Выбирает супервизора раньше, чем вообще заходит речь о протоколе.
2. **Плановая недоступность control plane делает ДОЛГОВЕЧНОСТЬ требованием к каналу.** Он лежит
несколько раз в неделю ПО ЗАМЫСЛУ, значит любой push-канал нуждается в буфере, ретраях и
дедупликации — а буфер, который вы построите, и есть журнал. Это и убивает сокеты, пайпы,
journald и localhost-HTTP как ОСНОВНОЙ канал.
3. **Атомарность денег** — «юнит сделан» и «юнит стоил $X» обязаны быть одной транзакцией в
собственном хранилище воркера.
4. **Радиус поражения** — см. выше.
5. **Standalone не должен быть режимом** — ветка `--no-server`, которую никто не гоняет, гниёт.
**Оговорка, которой у панели не было и которая касается только нас.** У нас при остановке платформы
движок останавливается ТОЖЕ (сигнал группе, а на stdout ещё и SIGPIPE), поэтому окна «движок пишет,
слушателя нет» не возникает и события не теряются. Аргумент №2 бьёт по нам не потерей событий, а
тем, что **каждый деплой платформы прерывает все идущие переводы**, ценой одного чанка каждый.
То есть вопрос сводится к продуктовому: **должен ли четырёхчасовой перевод переживать деплой
платформы?** Если да — журнал и отдельный юнит. Если нет — пайп законен, и цена названа явно.
## Что панель 2 добавила сверх первой
- **Разделять долговечный денежный канал и косметический канал живости:** `events.jsonl`
(fsync, append-only, с номерами) отдельно от `status.json` (атомарное переименование, ~2 с, явно
НЕдолговечный, из него никогда не биллить). 5/5.
- **Рамка outbox:** строка журнала — это ПРОЕКЦИЯ уже закоммиченной строки в БД воркера,
побайтово переиздаваемая после краха. Это превращает файл из «второй записи» в производный
артефакт и снимает половину возражений про fsync-дисциплину.
- **Перекос версий на длинной задаче:** воркер выкатывается еженедельно, задача идёт 30 часов —
значит задачу может ПРОДОЛЖИТЬ более новый воркер, чем её начал. Подняли 4 из 5; двое штампуют
версию воркера в каталоге задачи и отказываются резюмиться без явного
`--allow-version-change`. Для нас это прямо в канон §6 (детерминизм) и рядом с `brief_hash`.
- Один из пяти выбрал вариант «control plane читает приватную БД воркера» (в плече 1 его отвергали
5/5) — единственное расхождение с D39.85 §4 за три панели. Меньшинство, но зафиксировано.
## Развилка: одна машина
@ -130,11 +195,29 @@
## Открытое
- Строка 103 единого бэклога: транспорт потока. Ставка на stdout защитима (chunk-резюм делает цену
их сценария низкой), но должна быть записана как СОЗНАТЕЛЬНАЯ, с названной ценой будущего
переезда, а не как «переезд не окупается».
- **Вопрос владельцу, к которому всё свелось: должен ли идущий перевод переживать деплой
платформы?** Три панели, пятнадцать архитекторов, ноль голосов за нынешнюю форму — но их довод
упирается именно в это, а у нас есть смягчение (движок останавливается вместе с платформой, и
резюм стоит одного чанка). «Да» ⇒ журнал + отдельный юнит, движок перестаёт быть ребёнком.
«Нет» ⇒ нынешняя форма законна, и цена записана явно: каждый деплой прерывает все переводы.
- Строка 103 единого бэклога: транспорт потока. В ЛЮБОМ исходе ставка должна быть записана как
СОЗНАТЕЛЬНАЯ, с названной ценой, а не как «переезд не окупается» — эта формулировка опровергнута
плечом 2.
- Три денежные дыры выше (реконсилятор · `uncertain` · сверка с провайдером) — строками единого
бэклога, зона платформы их одна не закроет: половина работы в движке.
- Пиннинг к версионированному бинарю — туда же.
- Чекпойнт-плечо панели (`wf_2b45042d-115`) проверяет, меняет ли дешёвый резюм расклад. Результат
дописать сюда.
- Пиннинг к версионированному бинарю и **отказ резюмиться воркером другой версии** — туда же.
- Разделение `events` (долговечный, денежный) и `status` (косметический, не для биллинга) — принять
или отклонить при проектировании эмиттера, до его постройки.
## Что из этого следует методически
Три панели дали три разных урока о самой процедуре, и они дороже любого вердикта:
1. **Абстрактная формулировка обязана быть изолированной по-настоящему.** Запрет сети без запрета
файлов — не изоляция. Проверять постфактум, поагентно.
2. **Слабая аргументация ≠ неверная позиция.** Оркестратор сперва принял предложение зоны по её
ссылкам, потом отклонил его же, открыв ссылки и увидев, что они не держат. Обе итерации спорили
о качестве ссылок, а не о существе.
3. **Свой контраргумент проверять чужими руками.** Довод «chunk-резюм делает нынешнюю форму
нормальной» звучал убедительно, был предъявлен как решающий — и не пережил панели, которой этот
факт дали прямым текстом. Один из пятерых даже опознал его как приманку в постановке.