Bring the announced contract version to the canon the corrective minor moved, and keep the platform zone's account of what it learned before the rollback.

This commit is contained in:
heaven 2026-09-10 23:51:18 +03:00
parent 9c2eb31a56
commit 1c5bd2a00f
2 changed files with 172 additions and 1 deletions

View file

@ -49,6 +49,172 @@ committed_usd, which is what would let this side say "at most Y"». Движок
чтение оценочных строк из потока. Промт ещё не написан — если у вас есть довод против любой из четырёх
позиций, скажите ДО того, как я его напишу.
## ПАК «ДВЕ ОСТАНОВКИ» — ОТМЕНЁН ВЛАДЕЛЬЦЕМ, РАБОТА ОТКАЧЕНА (10.09, `textmachine-5c`)
> Промт `docs/PLATFORM_SOFT_STOP_SESSION_PROMPT.md` (редакция `ac9a24d`), вход HEAD `ac9a24d`.
> **Отменён владельцем по размаху**, а не по качеству: две остановки того не стоят. Новый инвариант
> объявлен тем же словом: **стоп остаётся ЖЁСТКИМ, терять деньги ДОПУСТИМО, но остановка обязана быть
> КОРРЕКТНОЙ** — без гонок и половинчатых состояний, с правильным возобновлением. Предмет переехал с
> «не сжечь деньги» на «не оставить мусор».
>
> ⚠ **Эта секция — не отчёт о построенном, а НАЙДЕННОЕ.** Код зоны возвращён к HEAD целиком (кроме
> константы версии контракта, см. ниже). Здесь остаётся то, что зона УЗНАЛА о собственном дереве:
> оно пережило пак и стоит дороже, чем стоил бы код.
### Что откачено и что оставлено
Возвращены к HEAD **26** путей в `platform/`. Удалены девять моих новых файлов:
`internal/gates/stopgrace_test.go` · `internal/httpapi/stopmode_test.go` ·
`internal/pgstore/migrations/00035_stop_mode.sql` · `internal/pgstore/migrations/00036_estimated_and_stop_ledger.sql` ·
`internal/pgstore/stopmode_test.go` · `internal/runner/stop_systemd_test.go` · `internal/runner/stop_test.go` ·
`internal/runs/estimated_test.go` · `internal/runs/stopmode_test.go`.
**Оставлено ОДНО и по прямому указанию оркестратора:** `internal/httpapi/capabilities.go`, константа
`ContractVersion`. Это чужой долг, взятый попутно: канон уехал на `0.13.1` актом `D39.235` п.6, а
константа осталась на `0.13.0`, и гейт `internal/gates.TestTheAnnouncedContractVersionIsTheOneTheCanonRatified`
краснел с момента ратификации — базовая линия зоны была КРАСНОЙ на входе, и не по вине пака.
В ходе пака константа стояла на `0.14.0` (свой минор: `mode` у `/stop` плюс `Run.stop_mode`, по
ратифицированному порядку «код первым»); с отменой пака этого минора нет, поэтому при откате она
приведена к **`0.13.1`** — к тому, чему билд действительно служит.
### ⛔ Что зона узнала о СЕБЕ — это и есть результат смены
Ниже разделено на «замерено исполнением», «прочитано в коде» и «латентно» намеренно: смешивать их
дороже обычного, потому что по ним заводятся строки. Адреса — по HEAD `ac9a24d`.
**1. `Runner.Stop` блокируется на весь грейс — ЗАМЕРЕНО.** `internal/runner/runner.go:195` зовёт
`systemctl --user stop` без `--no-block`, `runCommand` (`:106-112`) своего тайм-аута не имеет.
Пробник на транзиентном юните с `TimeoutStopSec=15`, процесс ловит SIGTERM и не выходит (systemd 259):
блокирующая форма вернулась через **15,058 с** (по SIGKILL), `stop --no-block` — через **0,014 с**,
юнит в `deactivating`. Сегодня движок на жёстком сигнале умирает за секунды, поэтому это невидимо.
Носители последствия: `WriteTimeout: 30 s` (`cmd/tmplatformd/main.go:208`) и свип (п.2).
**2. Израсходованный бюджет прогона свип считает ПРОВАЛОМ реконсиляции — прочитано в коде.**
`reconcileOne` (`internal/runs/reconcile.go:89-90`) заворачивает каждый прогон в `s.runBudget()`
(дефолт 60 с), и комментарий на `:92-115` говорит буквально «SPENDING the budget counts as a failure
even when nothing reported one». ⇒ блокирующий `Stop` внутри свипа (`:580`) даёт `ReconcileFailures++`,
отсрочку и через `StalledAfter` ЛОЖНУЮ тревогу оператору, плюс съеденную долю прохода для всех
остальных аккаунтов. ⚠ Прежняя формулировка оркестратора («свип встанет до двадцати минут») неверна
по механизму и исправлена актом `D39.236` п.5 по возражению этой смены.
**3. Повторный `stop` на юните в `deactivating` ИНЕРТЕН — ЗАМЕРЕНО, и это несущее свойство.**
Три стопа в t+0 / t+2 / t+4 против грейса 6 с: процесс получил **один** сигнал, юнит умер на
**t+6,04 с** (не t+10). То есть пере-выдача не шлёт второго сигнала и не перезапускает
`TimeoutStopSec`. ⭐ Ценность не в хорошей половине, а в том, что комментарий свипа
(`reconcile.go:576-579`) УЖЕ утверждает «asking again is free and idempotent», и это утверждение
держится на недокументированном поведении systemd, которое до этой смены никто не проверял.
⚠ И вторая половина того же комментария — «it is the only thing that closes the first case» —
**не выполняется**, если юнит уже начал останавливаться: пропущенный первый SIGTERM свип не чинит,
юнит стоит в `deactivating` до SIGKILL по грейсу (сегодня — до десяти минут прогона, который ничего
не делает, с зарезервированным холдом).
**4. Два SIGTERM подряд Go-процесс видит как ОДИН — ЗАМЕРЕНО, ратифицировано `D39.238`.**
gap 0 мс → оба сигнала увидены в **0 из 20** прогонов (в длинной серии слиплись 40 из 40);
gap 1 мс → **20 из 20**; буфер принимающего канала **8** — коалесценция живёт НИЖЕ канала, в
рантаймовом БИТЕ на сигнал. ⚠ Первый мой прибор давал «2 из 2» и подтверждал удобное: `kill` шёл из
шелла, и запуск двух процессов сам создавал зазор. Прибор чинен, число перевернулось.
Норма, выведенная из этого оркестратором: **замер, чей результат подтверждает удобное, проверяется на
ПРИБОР прежде, чем на предмет.**
**5. ⛔ `finish` рассчитывает деньги из ДО-ДРЕНАЖНОЙ выписки — ЛАТЕНТНО, но класс настоящий.**
`reconcile.go:955-958`: расчёт внутри `finish` объявлен оппортунистическим и получает тот самый `l`,
который проход прочитал ДО дренажа журнала. `finish` вручную освежает `l.Status`/`l.PausedReason`
перед вызовом (`:~918`) — то есть автор знал про класс и закрыл ровно два поля. **Любое число, которое
дренаж ЭТОГО ЖЕ прохода записал в строку попытки, для этого расчёта невидимо.**
⚠ Граница честности: на HEAD ничего не сломано — `settle` не читает из выписки ни одного поля, которое
пишет дренаж. Класс предъявлен исполнением только потому, что пак добавил такое поле: строка попытки
читала 9 оценочных строк, выписка — 0, и расчёт взял число, которое случайно знал запасной канал.
Поле ушло с паком, класс остался. Родственник в этом же файле уже стоил зоне дефекта: `freshRunState`
(`reconcile.go:~660`) написан ровно потому, что выписка не может знать, что записал её собственный дренаж.
**6. Обработчик `/stop` шлёт сигнал по имени юнита из СВОЕЙ ЖЕ выписки — прочитано в коде.**
`Service.Stop` (`reconcile.go:1541`) берёт `unit` из `RequestStop` и зовёт `Runner.Stop(ctx, unit)`.
Успел ли свип между этими двумя рестартовать попытку — сигнал уйдёт СТАРОМУ юниту. Самолечение есть
(следующий проход увидит `alive && StopRequestedAt != nil` и пере-выдаст стоп текущему, `:575-583`),
но приходит оно от СВИПА, а не от обработчика: окно равно одному тику, и всё это время пользователь
держит `202`, а прогон работает.
**7. Двойной клик по `/stop` — гонка по построению, сегодня безвредная.** `RequestStop`
(`internal/pgstore/runs.go:1298-1309`) — один UPDATE; под READ COMMITTED два одновременных нажатия
не различимы самим оператором, потому что `RETURNING` отвечает НОВОЙ строкой и не может отличить
собственную запись от чужой. Схлопывание в первый таймстамп делает оба нажатия одинаковыми, поэтому
вреда нет — ровно до той секунды, когда ответ или действие начнут зависеть от того, кто нажал первым.
Форма лечения, проверенная в отменённом паке: `select … for update` в одной транзакции с UPDATE.
**8. `stoppedOnRequest` сравнивает ДВОЕ РАЗНЫХ ЧАСОВ, и код это признаёт.** `reconcile.go:1137-1153`,
довод на `:~1000`: «the two clocks being compared are not even the same one — the marker carries the
host's, the intent the platform's». Сегодня цена ошибки — ЯРЛЫК. В любой конструкции, где от этого
ярлыка зависит ДЕЙСТВИЕ (перезапуск, возврат холда, возобновление), это гонка по построению.
**9. Каталог движка: потолка ожидания нет у пяти провайдеров из восьми — ЗАМЕРЕНО прибором.**
Программа на `config.LoadModels` + `Timeouts.Profile().DeadlineFor(B)` над снапшотом `git archive HEAD
backend` (ни байта записи в чужую зону): `attempt_max_s` задан у **3** (`deepseek` 1240 · `kimi` 1200 ·
`gemini` 1200), инертен у **5** (`zai` · `xai` · `openai` · `mistral` · `local`), и у них `DeadlineFor`
растёт с бюджетом линейно и сверху не ограничена. Максимум по каталогу при наибольшем бюджете
сегодняшней конфигурации (**35 200** ток. = редакторские `max(8000×2.2, 16000) = 17 600` с одним
удвоением по `regenerate_before_escalate: 1`) — **1240 с**, deepseek. Беcключевой `zai` перерастает его
при **43 400** ток., то есть при `edit_ceiling_out` ≈ 9 900 против сегодняшних 8 000 — одна строка
yaml, которая читается как ручка качества. Носитель — строка бэклога **383**.
**10. Dev и прод расходятся по грейсу в 20 раз, и это не объявлено нигде.**
`internal/ingest/supervisor.go:18``stopGrace = 30 * time.Second` (уезжает в `cmd.WaitDelay`, `:75`)
против `600 с` в проде (`internal/runner/runner.go:59`). Один и тот же движок против тех же платных
провайдеров получает на свёртку полминуты на dev-пути и десять минут на боевом.
**11. Пять открытых рядов реестра — про холды и остановку, и это один предмет, а не пять.**
Их печатает сам гейт зоны (`make check`, `internal/gates`, строка `ALARM PD-count: 15 open rows …
(baseline 15)`): **PD-418** (у settling-строки живого прогона нет ручки, а рантбук обещает обратное) ·
**PD-244** (единственный выход `settle`, оставляющий холд открытым МОЛЧА) · **PD-162** (книга с
пропавшим каталогом заклинивает прогон навсегда с открытым холдом) · **PD-465** (заявка на спавн
коммитится ДО подъёма юнита) · **PD-217** (незакрытый холд блокирует апгрейд движка бессрочно).
Под новым инвариантом это готовый предметный список, уже приоритизированный реестром.
**12. Что в дереве оказалось ЛУЧШЕ ожидаемого — и это тоже находка.** Гонка «кто пишет исход мёртвого
прогона» ЗАКРЫТА, и закрыта правильной формой: `FinishRun` возвращает `closed bool`, и `finish`
(`reconcile.go:903`, ветка `!closed` ~`:937`) разбирает случай «прогон уехал под этим проходом» явно;
второй расчёт получает `ErrNoReservation`, потому что резервация закрывается под `state = 'open'`.
Столкновение реконсайлера с `/stop` названо в трёх местах и в каждом закрыто: `finishStopped`
(`:678-685`), отказ рестарта по `ErrStopRequested` (`:1324`), пере-проверка заявки под замком в
`FinishUnspawnedStop` (`:~710`). ⇒ **модель «записать условно и прочитать вердикт» уже живёт в зоне**,
и новому инварианту стоит брать её образцом, а не изобретать другую.
**13. И одна развилка, которую новый инвариант обязан пересмотреть ЯВНО.**
`interruptedBySomeoneElse` (`reconcile.go:539`, функция `:1129`) ПЕРЕЗАПУСКАЕТ прогон по коду 5 без
записанного намерения. Это осознанное решение с записанным доводом (ребут против ручного
`systemctl stop`, асимметрия цен), а не дефект — но это главная развилка «правильного возобновления»,
и наследовать её молча нельзя.
### Числа смены
```
$ make check (все четыре гейта стенда закрыты: Postgres 55433 · движковый бинарь и шаблон из
`git archive HEAD backend` · достижимый пользовательский systemd · MemoryMax)
вход (до первой правки): MAKE-EXIT=2 · 19 `ok` · 1 FAIL · линтер «0 issues» · скипов 5
единственный красный — `internal/gates.TestTheAnnouncedContractVersionIsTheOneTheCanonRatified`
(«this build announces contract 0.13.0 and the ratified canon is 0.13.1»), НЕ по вине смены
скипы 5, условие у всех одно и названное: нет деплой-артефакта `configs/mining-contrast.zh.txt`
ПОСЛЕ ОТКАТА (последний прогон, снят после последней правки):
MAKE-EXIT=0 · 20 `ok` · 0 FAIL · линтер «0 issues» · скипов 5
`gofmt`/`go vet`/`sqlc diff` чисты · `ALARM PD-count: 15 open rows … (baseline 15)` — не сдвинут
красный входа ПОГАШЕН правкой константы: канон `0.13.1`, билд `0.13.1`
```
⚠ Гейты стенда закрыты все четыре намеренно: без `TM_PLATFORM_TEST_DSN` та же батарея печатает те же
`ok` и прячет ~370 тестов (эррата `08.09-д`). Счёт скипов печатается рядом со счётом `ok`.
### Что НЕ удалось
**Ничего из заказанного не сдано — пак отменён на середине, и это не оценка работы, а факт.**
Построенное и зелёное на момент отмены (грейс суммой с названными слагаемыми, `--no-block`, путь
второго сигнала через наблюдение `SubState`, режим в записи намерения с атомарной заявкой на второй
сигнал, чтение минора `1.4` из обоих каналов, пометка оценки на строке расчёта, живой systemd-стенд)
откачено целиком. Из адверсариального прохода §5.5 успели отработать три направления из шести —
(а) гонка двух нажатий, (б) остановка мёртвого юнита, (д) пере-выдача не должна эскалировать; (в)
чтение мягко остановленного прогона реконсиляцией, (г) идемпотентность `/stop` и (е) удлинённое окно
`deactivating` до отмены проверены не были. Мутационная проверка СВОИХ новых пинов — та, которую
норма зоны требует отдельным заходом, — не выполнена ни для одного из них: до неё смена не дошла.
Поэтому ни один пин выше в дереве не остаётся, и ни на один нельзя ссылаться как на проверенный.
## Состояние зоны на 08.09.2026
| Вопрос | Ответ |

View file

@ -36,7 +36,12 @@ import "net/http"
// the bar described in chapters while the wire sent units, `ordered_chapters: 0` promised where the
// wire sent 2, three values of a vocabulary that had four — because the NUMBER matched each time. The
// bump keeps the number from lying; it does not close the blind spot, which is unified backlog row 309.
const ContractVersion = "0.13.0"
//
// ⚠ 0.13.1 is the canon's CORRECTING minor (D39.235 §6) and this constant did not follow it for a day:
// the canon moved first and the gate stood red the whole time, which is the reverse of the order the
// paragraph above ratifies. The number is brought to the canon here rather than the other way round,
// because the canon is the one that is right — the wire this build serves is the 0.13.1 shape.
const ContractVersion = "0.13.1"
// Capabilities is what this deployment can do: one flat document, the same for every account.
type Capabilities struct {