From a58687febaa36b46fb724daa9b23987f58b3790e Mon Sep 17 00:00:00 2001 From: heaven Date: Thu, 10 Sep 2026 19:59:36 +0300 Subject: [PATCH] Record the second stop the engine does not have, alongside two rows a measurement closed and one the seam never carried: the estimate mark stops at the engine's own status. --- docs/BACKLOG.md | 7 +++---- docs/PROGRESS.md | 7 ++++--- docs/architecture/05-decisions-index.md | 3 ++- docs/architecture/05-decisions-log.md | 24 ++++++++++++++++++++++-- 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 7cbdba9b..4659d833 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -3,7 +3,7 @@ > **Единственный трекер проекта.** Здесь живут строки, на которые доки, промты и D-ноты ссылаются словами «строка N» / «строка бэклога N»: **ID строки стабилен навсегда**, не перенумеровывается и не переиспользуется (D39.80). Правки — только через оркестратора; каждая петля обязана иметь диспозицию: решено / отложено-с-записью / отклонено. > ⚠ **Это НЕ бэклог зоны `docs/`, а бэклог ПРОЕКТА.** У зон свои, с другими неймспейсами ID, и единый их строк не принимает (D39.84): платформа — [../platform/BACKLOG.md](../platform/BACKLOG.md) (`П-N`) и её регистр дефектов `platform/docs/DEFECT_REGISTER.md` (`PD-N`); фронт — [../frontend/docs/BACKLOG.md](../frontend/docs/BACKLOG.md) (`Ф-N`). > ⚠ **Состояние проекта — не здесь.** Очередь, курс, CURRENT-STATE, состояние паков и живая хроника — шапка [PROGRESS.md](PROGRESS.md). Здесь только долг и его диспозиции. -> - **СЧЁТ ОЧЕРЕДИ на 10.09 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **274** строк · зона бэкенд **136** строго / **183** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 2** (236 закрыта D39.175), платные прогоны разблокированы · «скоро» **126** (перечень — грепом по таблице, рукописный список снят D39.126) · гейт-строка эксп-22: 153 (плюс 150 — руки владельца; ⚠ 55 из гейтов СНЯТА — D39.198 п.5); строка 5 — остаток гейчен ре-пробой 74, носитель события теперь строка 188 (аудит D39.140); остальное «когда-нибудь». ⚠ Счёт — НИЖНЯЯ граница долга, не потолок (разбор — легенда таблицы ниже). +> - **СЧЁТ ОЧЕРЕДИ на 10.09 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **273** строк · зона бэкенд **135** строго / **182** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 1** (236 закрыта D39.175, **371** — актом D39.233 10.09), платные прогоны разблокированы · «скоро» **126** (перечень — грепом по таблице, рукописный список снят D39.126) · гейт-строка эксп-22: 153 (плюс 150 — руки владельца; ⚠ 55 из гейтов СНЯТА — D39.198 п.5); строка 5 — остаток гейчен ре-пробой 74, носитель события теперь строка 188 (аудит D39.140); остальное «когда-нибудь». ⚠ Счёт — НИЖНЯЯ граница долга, не потолок (разбор — легенда таблицы ниже). > - ⚠ **Переезд 06.09 (D39.218):** таблица вынесена из `PROGRESS.md` ДОСЛОВНО и ни один ID не сдвинут — **с единственным исключением: две ячейки (строки 220 и 306) тронуты РОВНО в номере якоря `docs/scripts/counts.py:566`→`:569` и `:592`→`:595`**, потому что цель сдвинул этот же коммит; текст ячеек не менялся, длины равны. В `PROGRESS.md` на прежнем месте оставлен заголовок-указатель, поэтому ссылки «секция „Бэклог“» доезжают в один хоп. ## Бэклог (ЕДИНЫЙ, собран 26.07, актуализация 04.08 D39.99/D39.101; правки — только через оркестратора) @@ -299,14 +299,13 @@ | 368 | **ОПЕРАТОРСКИЙ ЭКРАН ЗАНИЖАЕТ ОЦЕНОЧНЫЕ ТОКЕНЫ В 5 РАЗ.** `est_tokens` в `request_log` считается `r.estOutTokens(ch.Text)` по фертильности видимого перевода, а у DeepSeek размышление сидит ВНУТРИ `completion_tokens` ⇒ на строках `decode_error` стоит 2887/2854/3162, тогда как соседние попытки тех же чанков реально дали **16 000** (`finish=length`). Занижение **5.0–5.6×**. Читатель — `cmd/tmctl/render.go` («estimated-cost rows: N … ~M est. output tok»): оператор видит впятеро меньше правды. ⚠ В `cost_usd` это число НЕ входит — деньги считаются грантом, — то есть врёт легенда, а не счёт; но врёт она ровно тому, кто по ней решает, продолжать ли платить. Класс «прибор отвечает на свой вопрос» (`D39.202`). Находка консилиума с чистым Fable 5, 08.09 | бэкенд | скоро | легенда считает то же, что считает счёт, либо честно говорит, чего не знает | консилиум по строке 360, 08.09 | | 369 | ⛔ **ОСЬ ЭСКАЛАЦИИ D2 СЕГОДНЯ ФИКТИВНА: БРОНИРУЕТСЯ 32 000 ТОКЕНОВ, ДОСТИЖИМО ~19 400.** Реген удваивает `max_tokens` 16 000 → 32 000 и НЕ трогает время, а `attempt_s: 240` при 70–90 tok/s даёт потолок **17–21 тыс. токенов**. Замер по 20 регенам трёх DeepSeek-армов (`docs/experiments/23-editor-tier.md` + леджеры `probe-4axes/arms/`): доставлено первой попыткой 17, обрезано 3; максимум, когда-либо доставленный регеном — **19 354 = 60 % гранта**; **13 доставок из 17 уложились бы в ИСХОДНЫЙ грант 16 000** ⇒ реген «срабатывает» пере-броском длины размышления, а не бо́льшим бюджетом. ⚠ Цена не только в обрывах: резервация бронирует полные 32 000 ($0.127 выхода) при досягаемых ~$0.08 — фикция съедает headroom под книжным потолком. ⇒ дедлайн от бюджета (строка **360**) — не подкрутка таймаута, а то, что делает эту ось настоящей; закрывать вместе с ней. Категорика, которая держится: реген не может использовать верхние ~40 % выданного, и всякий, кому эскалация РЕАЛЬНО нужна, обрезается по построению | бэкенд | скоро | эскалация досягаема по времени либо не бронирует недостижимое | консилиум по строке 360, 08.09 | | 370 | **Anthropic-адаптер не несёт капа ретраев, который есть у общего транспорта.** `backend/internal/llm/provider_anthropic.go` возвращает `BilledDecodeError` с `retryable=true` без ограничителя `billedDecodeSeen`, который стоит в `httpllm.go` — то есть на этом адаптере оплаченный 2xx может повториться до `max_attempts`, а не один раз. ⚠ **Боевой цены сегодня НОЛЬ:** поверхность deprecated, Anthropic убран из стека 04.07, вызовов по этому пути в прогонах нет. ⇒ сознательно вынесено ИЗ пака «вызов, который оборвали мы» (строка 360) по доводу ревью: пак получил бы тесты и мутации на мёртвую поверхность. Чинить при воскрешении адаптера — тогда же и проверять, применим ли к нему признак доставки | бэкенд | когда-нибудь | адаптер получает тот же кап ЛИБО удалён вместе с поверхностью | ревью пака «вызов, который оборвали мы», 08.09 | -| 371 | ⛔ **`WroteRequest` НЕ ДОКАЗЫВАЕТ, ЧТО ПРОВАЙДЕР ПРИНЯЛ ЗАПРОС — и ошибка идёт в сторону ПЕРЕПЛАТЫ ЧИТАТЕЛЯ, единственную, которую `D39.196` п.2а называет недопустимой.** Признак доставки, на котором стоит вся денежная граница пака «вызов, который оборвали мы» (строка **360**), срабатывает, когда байты ушли в НАШ сокет, а не когда их прочитал провайдер. Замер движковой сессии 08.09 (25 прогонов, два независимых серверных счётчика): **3 из 25 списывают оценку за вызов, которого не было** — `elapsed=0s`, `bytes_read=0`. ⚠ Отдельно замерено, что расширение признака заставляет платить и за ОТКАЗЫ: 22 из 25, на боевом пути **$0.80 за запрос, который провайдер отклонил** (эту половину сессия ПОЧИНИЛА, остаётся первая). ⛔ Чинится порогом по времени жизни вызова — а порог по латентности пак ЗАПРЕЩАЕТ, и строка **331** требует ЗАМЕРЕННОГО порога, не назначенного. ⇒ **вопрос владельцу, один: платить ли за вызов, чьи байты ушли в сокет, но провайдер их не прочитал.** До ответа класс живёт с известной переплатой | бэкенд | блокер-очереди для решения о деньгах (кода не блокирует) | порог замерен ЛИБО признак различает «ушло в сокет» и «прочитано» иначе | сдача пака «вызов, который оборвали мы» 08.09 | | 372 | **СТРАЖ ОТМЕНЫ ГЛОТАЕТ НАСТОЯЩУЮ ДЕНЕЖНУЮ ОСТАНОВКУ, когда прогон отменил не человек, а упавший сосед.** Платформа получит `failed` вместо `paused`, а книга без денег упрётся в потолок на ПЕРВОЙ резервации следующего прогона. Сессия стража оставила и была права: ратифицированный комментарий ветки `waitAborted` требует ровно этого для ОБОИХ случаев. ⇒ предмет не движковый, а **продуктовое решение о шве**: должна ли платформа отличать «остановлено человеком» от «остановлено падением соседа» | бэкенд | скоро | шов различает две отмены ЛИБО объявлено, что не различает, с ценой | сдача пака «вызов, который оборвали мы» 08.09 | | 373 | **НА HTTP/2 НАШ ДЕДЛАЙН ИНЕРТЕН, если тело запроса больше окна пира, а провайдер отвечает ранним 2xx, не дочитав его.** Замер движковой сессии: вызов длится столько, сколько держит сервер — **4 с и 8 с при дедлайне 700 мс**, — и всё уезжает в `decode_error`, ту самую дверь, которую пак 360 объявил закрытой. Механизм: запись паркуется в `awaitFlowControl` на `sync.Cond`, которую контекст НЕ будит. ⚠ Дыра ПРЕДШЕСТВУЕТ паку и требует пирa, отвечающего не дочитав тело (НЕ форма DeepSeek). Лечится `http2.Transport.WriteByteTimeout`, но его значение — НОВЫЙ порог, а пороги назначать нельзя | бэкенд | когда-нибудь | дедлайн действует и на записи тела ЛИБО класс объявлен недостижимым с замером | сдача пака «вызов, который оборвали мы» 08.09 | -| 374 | ⚠ **СУЖЕНА 10.09: половина про ЕДИНИЦУ снята работой по паку 360 — остановленная позиция больше не решает судьбу редакторской единицы. ОСТАЁТСЯ вердикт ГЛАВЫ, и он продуктовый.** **НАЖАТИЕ ОСТАНОВКИ ПОРТИТ ВЕРДИКТ ГЛАВЫ.** Вердикт главы считается по СЧЁТУ флагов, а остановленная позиция флаг получает ⇒ одна остановка даёт «attention», две — «fail». То есть человек, нажавший стоп, ухудшает оценку КАЧЕСТВА главы, к которому его нажатие отношения не имеет. Продуктовое: должен ли ранг флага «остановлено» вообще участвовать в вердикте, или у остановки своя полоса. ⚠ Исчезает само, если денежная половина пака «вызов, который оборвали мы» (строка **360**) будет урезана: класс `cancelled` уйдёт вместе с ней. Найдено критиком полноты седьмого круга; диспозиция оркестратора жила письмом и носителя не имела до `D39.231` | бэкенд | скоро | остановка не двигает вердикт качества ЛИБО объявлено, что двигает, с доводом | седьмой круг ревью пака 360, 08.09 | | 375 | **ВЫГРУЗКА ЧИТАЕТ ОТМЕНЁННУЮ ПОЗИЦИЮ КАК ВЫПАВШИЙ КУСОК ТЕКСТА.** `memberDrops` считает строку `cancelled` выпавшим членом редакторской единицы — то есть выносит КОНТЕНТ-вердикт по факту нажатия кнопки: читателю сообщается, что из единицы пропал текст, тогда как пропала лишь незаконченная работа. Семантика экспорта, не транспорт. ⚠ Исчезает само вместе с классом `cancelled`, если денежная половина строки **360** будет урезана. Найдено критиком полноты седьмого круга; диспозиция оркестратора жила письмом и носителя не имела до `D39.231` ⚠ **ДОБАВКА 10.09 (замер движковой зоны по запросу оркестратора, подтверждён ключом БД).** Правка «применить `resolvedForResume` внутри `memberDrops`» ПОЛНА для этого пути (живой вызов один — `backend/internal/pipeline/export.go:307`) и попутно чинит артефакт порядка: внутренний цикл `status.go:538-543` оставляет ПОСЛЕДНЮЮ флагнутую черновую строку, а строки приходят `ORDER BY … stage`, поэтому при двух черновых стадиях `cancelled` может перебить контентную причину; фильтр возвращает контентную. Случай «сверху лёг `cancelled`» НЕВОЗМОЖЕН: строка апсертится по `(book_id, chapter, chunk_idx, stage)` (`backend/internal/store/chunkstatus.go:92`), а контентно флагнутая стадия не пере-выполняется (резюм-гейт `backend/internal/pipeline/stagerun.go:93`). ⛔ **НО правка меняет ЛОЖЬ на МОЛЧАНИЕ, и это не закрытие:** если единственный флаг члена — `cancelled`, после фильтра `DroppedMembers` = 0 и `DroppedReason` пуст (`export.go:320-322`), единица уходит `ok`, молча не досчитавшись текста члена. ⇒ закрывать надо ТРЕТЬИМ состоянием («единица неполна из-за остановки»), а не одним фильтром. Окно узкое: нужна комбинация «финальная строка от прежнего прогона + редрайв члена + стоп» (`export.go:174`) | бэкенд | скоро | единица, неполная ИЗ-ЗА ОСТАНОВКИ, отличима и от выпавшего контента, и от полной: ни ложного контент-вердикта, ни молчания | седьмой круг ревью пака 360, 08.09; добавка — замер зоны 10.09 | | 376 | **ДОСТАВЛЕННЫЙ ОБРЫВ ДО ЗАГОЛОВКОВ КНИЖИТСЯ НУЛЁМ — и мы НЕ ЗНАЕМ, у скольких провайдеров это срабатывает.** Деньги за оборванный нами вызов считаются по `answered && afterHeaders` (`backend/internal/llm/attemptcut.go`), то есть единственный оплачиваемый обрыв — пришедший через ошибку ЧТЕНИЯ ТЕЛА. Обрыв до первого байта ответа (наш дедлайн, стоп родителя, разрыв сокета) даёт **$0** и не виден даже в оценочной паре (`paidtail.go` пропускает `CostUSD <= 0`). ⚠ **Сужение СОЗНАТЕЛЬНОЕ и ратифицировано** (`D39.230`, разведение предикатов): довод замером — 3 из 25 отменённых в полёте сеттлили оценку за запрос, которого не видел ни один хендлер, то есть переплата ЧИТАТЕЛЯ, запрещённая `D39.196` п.2а. ⛔ **Открыт РАЗМЕР остатка, и он неизвестен по построению:** провайдеров **8** (замер по `configs/models.yaml`), ранний `200` задокументирован ровно у ОДНОГО (DeepSeek), а про поведение заголовков у остальных СЕМИ в репозитории не сказано НИЧЕГО — греп класса по странице квирков даёт 0 при 250 строках файла. ⇒ верное утверждение не «сломано у семи», а **«неизвестно для семи»**: если заголовки у них приходят рано, путь почти не срабатывает; если поздно — головной класс пака у них не закрыт вовсе. ⚠ Замерить без платных вызовов нельзя, и постфактум из телеметрии тоже: движок не пишет время до первого байта успешных вызовов. ⇒ первый шаг дешёвый и $0 — начать писать это время | бэкенд | скоро | время до первого байта пишется; размер остатка назван числом, а не «неизвестно» | приёмка пака «вызов, который оборвали мы» 10.09 | | 377 | **ОПЛАЧЕННЫЙ ОБРЫВ, ЗА КОТОРЫМ РЕТРАЙ УСПЕЛ, ИСЧЕЗАЕТ БЕССЛЕДНО: $0 и ни одного следа.** `backend/internal/llm/httpllm.go` — на успешном ретрае `if err == nil { return resp, nil }`, и накопленный `owedCut` ВЫБРАСЫВАЕТСЯ. Замер приёмки: сервер спрошен ДВАЖДЫ (первый вызов доставлен, подтверждён и оборван), вызывающий получает чистый успех и о первом не узнаёт ничего — ни строки леджера, ни оценки, ни пометки. ⚠ Это ровно предмет пака (платим дважды за одну генерацию), но пак его НЕ заказывал: §4.2 требовал деньги «на исчерпании», а здесь исчерпания нет — ретрай удался. Ни одна из девяти фикстур этого пути не покрывает: `TestABrokenConnectionAfterDeliveryIsRetriedOnceAndOnlyOnce` роняет ОБЕ попытки | бэкенд | скоро | оплаченный обрыв виден в леджере независимо от исхода ретрая | приёмка пака «вызов, который оборвали мы» 10.09 | | 378 | **ПИН «НЕ-2XX — НЕ ПОКУПКА» ЛОВИТ МУТАНТА НЕ ТЕМ АССЕРТОМ: денежные проверки в нём не исполняются ни разу.** Каталожная мутация `CUTCALL-a-non-2xx-becomes-a-purchase` (батарейная, цель — `backend/internal/llm/httpllm.go`, ветвь статуса `if resp.StatusCode < 200 || resp.StatusCode >= 300` → `if false`). Пере-снято ОРКЕСТРАТОРОМ 10.09 на копии: тест краснеет, но ТЕКСТОМ `cutcall_test.go:584: a provider failing every attempt must surface as an infra failure` — ассертом об исходе ПРОГОНА. `t.Fatal` останавливает тест ДО трёх проверок, ради которых пин написан (`committed`, `reserved`, `checkpoints`), и они не исполняются. Под мутацией движок сеттлит **$0.001056 как ОЦЕНКУ за 500-ку** — ровно ту утечку, которую доккоммент теста называет, — и текст падения об этом молчит. ⚠ Класс ратифицирован (`D39.217` п.2в, `D39.232` п.6в) — здесь открыт НОСИТЕЛЬ | бэкенд | скоро | мутант краснеет ассертом О ДЕНЬГАХ: посылка «прогон упал» проверяется так, чтобы не глотать денежные проверки | приёмка пака «вызов, который оборвали мы» 10.09 (С8) + пере-замер оркестратора | | 379 | **ДВА ПИНА ПАКА НЕ УТВЕРЖДАЮТ СВОЕЙ ПОСЫЛКИ — названный сценарий может не состояться, и они этого не скажут.** **(а)** `backend/internal/pipeline/cutcall_test.go:402` — две строки таблицы девяти («after 2xx · cancelled by the operator» и «before headers · cancelled by the operator») разводятся ОКНОМ СТЕННЫХ ЧАСОВ: `<-srv.arrived` + `time.Sleep(50 * time.Millisecond)` против `attempt_s: 1`. По какую сторону границы заголовков сел `cancel()`, не утверждается ничем. Замер приёмки на мутанте: **2 красных из 8** — отмена обгоняла разбор заголовков, и строка сходилась «ноль к нулю» (`D39.232` п.6а). **(б)** `backend/internal/llm/attemptcut_test.go` — `TestARefusalIsNotAPurchaseEvenWhenTheReplyOutrunsTheWrite` утверждает только `err != nil` и `!errors.As(err, &cut)`; что ответ ДЕЙСТВИТЕЛЬНО обогнал запись (шестимегабайтное тело не дописалось, `WroteRequest` не сработал), не проверяет ничто — тест зелен и на сценарии, где запись успела, то есть на другом предмете | бэкенд | скоро | каждый из двух пинов ПЕЧАТАЕТ величину, доказывающую пройденную границу, и падает, когда сценарий не состоялся | приёмка пака «вызов, который оборвали мы» 10.09 (С5, С6) | | 380 | **У ДЕНЕЖНОГО УТВЕРЖДЕНИЯ «ПЕРЕ-ДЕЛКА НЕ УДВАИВАЕТ БЮДЖЕТ» ОДИН НОСИТЕЛЬ, А НЕ ПЯТЬ.** Колонка `wantSameBudget` стоит в четырёх строках таблицы девяти (`backend/internal/pipeline/cutcall_test.go:333,340,355,361`), но осей не разводит: обжог случается ВНУТРИ `runAttempt`, а `maxTokens` считается в `runStage` ДО вызова, на итерации `attempt == 0`, где номер попытки и число удвоений равны по причине, к предмету отношения не имеющей. Пере-снято ОРКЕСТРАТОРОМ 10.09 на копии под каталожной мутацией `CUTCALL-the-redo-doubles-the-budget` (`maxTokensForAttempt(baseMaxTokens, escalations)` → `..., attempt`): таблица девяти — **0 красных из 8** (контроль: прибор запустил 10 подтестов, то есть спрашивал существующее), весь пакет `internal/pipeline` — ровно **ОДИН** красный тест, `TestABurnFollowedByARegenerationDoesNotOverBuy` (`cutcall_test.go:1435`). ⚠ Сам носитель ПОЧИНЕН паком (посылку теперь утверждает: «run 1 must leave exactly ONE checkpoint», «…is three checkpoints»), поэтому дыры сегодня нет — есть ЕДИНСТВЕННОСТЬ | бэкенд | когда-нибудь | утверждение держат ≥2 независимых носителя ЛИБО колонка `wantSameBudget` снята как ничего не меряющая, и это записано | приёмка пака «вызов, который оборвали мы» 10.09 (С3) + пере-замер оркестратора | -| 381 | ⛔ **ДВУХ ОСТАНОВОК НЕТ: у человека одна кнопка, и она ЖЁСТКАЯ.** Слово владельца 09–10.09 («должно быть два стопа, один жёсткий… и второй мягкий, который не обрывает всё сразу, чтоб деньги зазря не списывались») носителя не получило — ни ноты, ни строки, ни заказа в паке; жило перепиской и умерло вместе с сессией (тот же класс, что `D39.231` п.1). **Замерено 10.09 оркестратором и подтверждено движковой зоной:** единственная регистрация сигнала — `backend/cmd/tmctl/main.go:207` `signal.NotifyContext`, она отменяет контекст прогона ⇒ вызовы в полёте рвутся, доставленные книжатся по оценке и метятся `cancelled`. **Мягкий ГЛАГОЛ уже построен** — `latched` (`backend/internal/pipeline/waverun.go:353-363`, дословно «a different verb from cancel()»: фидер перестаёт раздавать индексы, начатое доигрывается под неотменённым контекстом), но дёргает его только денежный потолок (строка 277), и он ЛОКАЛЕН для одного `runWave` (живых вызовов два: `waverun.go:140`, `:285`) ⇒ кнопке нужен ПРОГОННЫЙ источник, который волна консультирует. **Что ещё названо замером зоны:** (а) мягкий стоп контекст не отменяет ⇒ `errors.Is(err, context.Canceled)` ложно и `exitCode` (`cmd/tmctl/main.go:117`) отдаёт 0/2 — «я остановил» машинно неотличимо от «книга кончилась»; форма для своего кода и поля есть у потолка (`CeilingHalt` → 4 → `events.go:423`), но прецедент против отдельного `outcome` тоже есть (`runevents.go:261-264`); (б) банковые проходы идут ВНЕ волны (`terminologist.go:348`, `:907`, зовутся из `mining.go:166`) и латчем не останавливаются — их естественная точка попартийный приём `terminologist.go:944-970`; (в) резюм уже верен и правки не требует (`waverun.go:449-452`); (г) `--max-units` слотов не тратит, но недоигранный грант должен публиковаться в `Finished.Volume`; (д) ⛔ **второй Ctrl-C сегодня НЕ ДЕЛАЕТ НИЧЕГО** — `stop()` стоит на `defer` (`main.go:208`), горутина `NotifyContext` после первого сигнала вышла, второй сигнал попадает в буфер и никем не читается ⇒ «второе нажатие ужесточает» — НОВОЕ поведение, а не восстановление дефолта. ⚠ **Продуктовая цена, которую владелец должен знать ДО решения:** мягкий стоп доигрывает начатое, а один вызов может держаться до `attempt_max_s` (~20 мин на deepseek) — оператор, не знающий этого, нажмёт второй раз и потеряет ровно те деньги, ради которых мягкий стоп заводился | бэкенд | скоро | у человека ДВЕ различимые остановки: мягкая (новое не начинаем, начатое доигрываем, пометок `cancelled` не возникает) и жёсткая; обе различимы машинно — кодом выхода и признаком в кадре `finished`, чтобы платформа не читала остановку как конец книги | слово владельца 09–10.09 + замер оркестратора и движковой зоны 10.09 | +| 381 | ⛔ **ДВУХ ОСТАНОВОК НЕТ: у человека одна кнопка, и она ЖЁСТКАЯ.** Слово владельца 09–10.09 («должно быть два стопа, один жёсткий… и второй мягкий, который не обрывает всё сразу, чтоб деньги зазря не списывались») носителя не получило — ни ноты, ни строки, ни заказа в паке; жило перепиской и умерло вместе с сессией (тот же класс, что `D39.231` п.1). **Замерено 10.09 оркестратором и подтверждено движковой зоной:** единственная регистрация сигнала — `backend/cmd/tmctl/main.go:207` `signal.NotifyContext`, она отменяет контекст прогона ⇒ вызовы в полёте рвутся, доставленные книжатся по оценке и метятся `cancelled`. **Мягкий ГЛАГОЛ уже построен** — `latched` (`backend/internal/pipeline/waverun.go:353-363`, дословно «a different verb from cancel()»: фидер перестаёт раздавать индексы, начатое доигрывается под неотменённым контекстом), но дёргает его только денежный потолок (строка 277), и он ЛОКАЛЕН для одного `runWave` (живых вызовов два: `waverun.go:140`, `:285`) ⇒ кнопке нужен ПРОГОННЫЙ источник, который волна консультирует. **Что ещё названо замером зоны:** (а) мягкий стоп контекст не отменяет ⇒ `errors.Is(err, context.Canceled)` ложно и `exitCode` (`cmd/tmctl/main.go:117`) отдаёт 0/2 — «я остановил» машинно неотличимо от «книга кончилась»; форма для своего кода и поля есть у потолка (`CeilingHalt` → 4 → `events.go:423`), но прецедент против отдельного `outcome` тоже есть (`runevents.go:261-264`); (б) банковые проходы идут ВНЕ волны (`terminologist.go:348`, `:907`, зовутся из `mining.go:166`) и латчем не останавливаются — их естественная точка попартийный приём `terminologist.go:944-970`; (в) резюм уже верен и правки не требует (`waverun.go:449-452`); (г) `--max-units` слотов не тратит, но недоигранный грант должен публиковаться в `Finished.Volume`; (д) ⛔ **второй Ctrl-C сегодня НЕ ДЕЛАЕТ НИЧЕГО** — `stop()` стоит на `defer` (`main.go:208`), горутина `NotifyContext` после первого сигнала вышла, второй сигнал попадает в буфер и никем не читается ⇒ «второе нажатие ужесточает» — НОВОЕ поведение, а не восстановление дефолта. ⚠ **Продуктовая цена, которую владелец должен знать ДО решения:** мягкий стоп доигрывает начатое, а один вызов может держаться до `attempt_max_s` (~20 мин на deepseek) — оператор, не знающий этого, нажмёт второй раз и потеряет ровно те деньги, ради которых мягкий стоп заводился ⛔ **ДОБАВКА 10.09: два носителя УЖЕ обещают мягкий стоп, которого нет.** Контракт — `docs/architecture/14-api-contract/openapi.yaml:762` «The product "stop" action; finished work is kept and not paid for again»: жёсткий стоп это нарушает, летящая единица книжится по оценке и пере-делается на резюме (`backend/internal/pipeline/cutcall.go:208`), то есть покупается ДВАЖДЫ. Платформа — `platform/internal/runner/runner.go:56-58` («the engine … finishes the in-flight chunk before exiting») и `:163-164`: оба утверждения о движке ложны. И числовой конфликт: `stopGrace = 10 * time.Minute` (`runner.go:59`) МЕНЬШЕ потолка ожидания вызова (`attempt_max_s` 1240 с) ⇒ как только SIGTERM станет мягким, systemd будет убивать законное ожидание, а SIGKILL не оставляет ни пометки, ни сеттла. ⇒ пак движка ПАРНЫЙ платформенному, порознь не лендятся | бэкенд | скоро | у человека ДВЕ различимые остановки: мягкая (новое не начинаем, начатое доигрываем, пометок `cancelled` не возникает) и жёсткая; обе различимы машинно — кодом выхода и признаком в кадре `finished`, чтобы платформа не читала остановку как конец книги | слово владельца 09–10.09 + замер оркестратора и движковой зоны 10.09 | +| 382 | ⛔ **ПОМЕТКА «ЭТО ОЦЕНКА» — УСЛОВИЕ СЛОВА ВЛАДЕЛЬЦА (`D39.230` п.1) — ПО ШВУ НЕ ЕДЕТ И ПОТРЕБИТЕЛЯ НЕ ИМЕЕТ.** Владелец разрешил списывать с читателя по ОЦЕНКЕ при условии, что пометка стоит. В движке она есть: `backend/internal/pipeline/status.go:321` — `estimated_rows` / `estimated_usd` в `status --json`. Дальше она обрывается. Замер 10.09 с контролем: в кадрах событий шва (`backend/internal/runevents/runevents.go`) слова `estimated` **0 хитов** (контроль: `committed` в том же файле — **6**; денежный кадр несёт один `committed_micro_usd`, `runevents.go:244`); в `platform/internal` вне тестов — **2 хита, оба КОММЕНТАРИИ**, причём `pgstore/credits.go:235` прямо просит эту величину: «publish the count and sum of estimated-price rows beside committed_usd, which is what would let this side say "at most Y"» (контроль: `committed_micro` — 1 хит; go-файлов вне тестов прибор прочёл **81**). ⇒ условие исполнено на ОДНОМ канале из двух, и читателя, ради которого пометка ставилась, у неё нет: платформа не может сказать пользователю «не больше Y». ⚠ Пара к строке **381**: и то и другое — признак, который обязан ехать в кадре `finished`/`spend`, а не только в `status --json` | бэкенд | скоро | платформа ЧИТАЕТ счёт и сумму оценочных строк из потока и может назвать пользователю верхнюю границу списания | акт `D39.233` п.7, замер оркестратора 10.09 по разбору чистого Fable 5 | diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index e14226f2..24629cb7 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -1,6 +1,6 @@ # Журнал прогресса -> **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-09-08, голова D39.232 — КУРС: движок и платформа до «работает и отдаёт результат», фронт ЗАМОРОЖЕН и P7 его НЕ размораживает (D39.147). **ОЧЕРЕДЬ №23** (единственный носитель — здесь; роль передана 06.09, №22 закрыт нотой передачи D39.217; ⚠ испр. 06.09: коммит передачи `c992ee5` бампнул голову и НЕ тронул номер — гейта на номер очереди нет вовсе, `counts.py` сверяет только голову, поэтому носитель разошёлся молча). ⚠ **ЗАКАЗ ВЛАДЕЛЬЦА 01.09 — ИСПОЛНЕН, испр. 08.09 (висел как «первым» неделю после исполнения):** (0а) ревизия документации на протухшее ОТРАБОТАНА 01.09 воркфлоу `docs-staleness-revision-A` (15 срезов), провенанс находок — `D39.185`; (0б) планы доработок в бэкенд и платформу — исполняются ПАКАМИ, за 07–08.09 закрыты два движковых (`D39.225`, `D39.226`); (0в) вынос неактуального в архив идёт батчами `DOC_CLEANUP_PLAN.md` (Б14/Б15/Б17 живы). ⇒ строка ниже — не заказ, а история: (0а) ревизия документации на ПРОТУХШЕЕ — по всем зонам; (0б) планы доработок в БЭКЕНД и ПЛАТФОРМУ; (0в) вынос неактуального в АРХИВ (`docs/archive/`, `platform/docs/archive/`) — за сутки 31.08 закрыто много, и часть носителей стала историей. ⚠ Трезвость по масштабу (пере-считано 02.09): **94** живых дока в `docs/` (пере-счёт 04.09: `find docs -name '*.md' -not -path 'docs/archive/*' | wc -l`), 8 в `platform/docs`, 9 в `backend/docs` — ⚠ из 94 ВРЕМЕННЫЙ остался ОДИН (`DOC_CLEANUP_PLAN`, живой до закрытия батчей Б14/Б15/Б17); два прежних временных уехали в `archive/reports/` 02.09; счёт бэклога — бюллетенем ниже, открытых рядов регистра платформы — 109 (major 1), всего рядов 465 (пере-счёт `python3 docs/scripts/counts.py`; с 04.09 оба числа под гардом `--check`, прежние 95/3 разошлись молча) ⚠ (ревизией 02.09 ряды **154** и **157** переведены из «скоро» в «когда-нибудь»: их гейтом стоял первый холодный прогон, он ОТРАБОТАЛ 31.08 и оба предусловия оказались другими — разбор в самих ячейках, ни одна НЕ закрыта). ⚠ Числа доков `counts.py` НЕ сторожит — при переносе файлов пере-считывать руками командой `find docs -name '*.md' -not -path 'docs/archive/*' | wc -l`. ⚠ И предупреждение о МЕТОДЕ, купленное сменой №21: «выглядит протухшим» ≠ «протухло». Три факта оркестратора опровергнуты ЗАМЕРОМ сессий, а якоря `15-money-path.md` в девяти случаях из двенадцати РОДИЛИСЬ верными и сгнили дрейфом — то есть ревизия обязана быть исполнением, а не чтением. ⚠ **ОЧЕРЕДЬ, унаследованная от №21** (три лендинга 31.08 — секция «СОСТОЯНИЕ ПАКОВ» ниже): **(1) ~~РАЗРЫВ ЦИКЛА~~ ЗАМКНУТ ЖИВЬЁМ 04.09** — пользователь получил EPUB настоящего ПЛАТНОГО перевода ЧЕРЕЗ API, `epubcheck` 5.3.0 на СКАЧАННОМ файле 0/0/0/0 (книга `bk_SS5VES2JELESJSTR`, потрачено $0.278319 из гранта $0.60 при потолке пака $1.5, санкция D39.189). Дверь выдачи построена и проверена исполнением: `202`+`Location`, поллинг с `Retry-After`, Range 206 · второй клиент 200 · аноним 401 · чужая книга 404, TTL с GC, идемпотентность третьего создающего вызова. Все ТРИ сценария строки 216 предъявлены живьём (подпись банка · halt на потолке с exit 4 · `409 run_not_resumable/ceiling_reached` и лечение новым прогоном). +> **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-09-08, голова D39.233 — КУРС: движок и платформа до «работает и отдаёт результат», фронт ЗАМОРОЖЕН и P7 его НЕ размораживает (D39.147). **ОЧЕРЕДЬ №23** (единственный носитель — здесь; роль передана 06.09, №22 закрыт нотой передачи D39.217; ⚠ испр. 06.09: коммит передачи `c992ee5` бампнул голову и НЕ тронул номер — гейта на номер очереди нет вовсе, `counts.py` сверяет только голову, поэтому носитель разошёлся молча). ⚠ **ЗАКАЗ ВЛАДЕЛЬЦА 01.09 — ИСПОЛНЕН, испр. 08.09 (висел как «первым» неделю после исполнения):** (0а) ревизия документации на протухшее ОТРАБОТАНА 01.09 воркфлоу `docs-staleness-revision-A` (15 срезов), провенанс находок — `D39.185`; (0б) планы доработок в бэкенд и платформу — исполняются ПАКАМИ, за 07–08.09 закрыты два движковых (`D39.225`, `D39.226`); (0в) вынос неактуального в архив идёт батчами `DOC_CLEANUP_PLAN.md` (Б14/Б15/Б17 живы). ⇒ строка ниже — не заказ, а история: (0а) ревизия документации на ПРОТУХШЕЕ — по всем зонам; (0б) планы доработок в БЭКЕНД и ПЛАТФОРМУ; (0в) вынос неактуального в АРХИВ (`docs/archive/`, `platform/docs/archive/`) — за сутки 31.08 закрыто много, и часть носителей стала историей. ⚠ Трезвость по масштабу (пере-считано 02.09): **94** живых дока в `docs/` (пере-счёт 04.09: `find docs -name '*.md' -not -path 'docs/archive/*' | wc -l`), 8 в `platform/docs`, 9 в `backend/docs` — ⚠ из 94 ВРЕМЕННЫЙ остался ОДИН (`DOC_CLEANUP_PLAN`, живой до закрытия батчей Б14/Б15/Б17); два прежних временных уехали в `archive/reports/` 02.09; счёт бэклога — бюллетенем ниже, открытых рядов регистра платформы — 109 (major 1), всего рядов 465 (пере-счёт `python3 docs/scripts/counts.py`; с 04.09 оба числа под гардом `--check`, прежние 95/3 разошлись молча) ⚠ (ревизией 02.09 ряды **154** и **157** переведены из «скоро» в «когда-нибудь»: их гейтом стоял первый холодный прогон, он ОТРАБОТАЛ 31.08 и оба предусловия оказались другими — разбор в самих ячейках, ни одна НЕ закрыта). ⚠ Числа доков `counts.py` НЕ сторожит — при переносе файлов пере-считывать руками командой `find docs -name '*.md' -not -path 'docs/archive/*' | wc -l`. ⚠ И предупреждение о МЕТОДЕ, купленное сменой №21: «выглядит протухшим» ≠ «протухло». Три факта оркестратора опровергнуты ЗАМЕРОМ сессий, а якоря `15-money-path.md` в девяти случаях из двенадцати РОДИЛИСЬ верными и сгнили дрейфом — то есть ревизия обязана быть исполнением, а не чтением. ⚠ **ОЧЕРЕДЬ, унаследованная от №21** (три лендинга 31.08 — секция «СОСТОЯНИЕ ПАКОВ» ниже): **(1) ~~РАЗРЫВ ЦИКЛА~~ ЗАМКНУТ ЖИВЬЁМ 04.09** — пользователь получил EPUB настоящего ПЛАТНОГО перевода ЧЕРЕЗ API, `epubcheck` 5.3.0 на СКАЧАННОМ файле 0/0/0/0 (книга `bk_SS5VES2JELESJSTR`, потрачено $0.278319 из гранта $0.60 при потолке пака $1.5, санкция D39.189). Дверь выдачи построена и проверена исполнением: `202`+`Location`, поллинг с `Retry-After`, Range 206 · второй клиент 200 · аноним 401 · чужая книга 404, TTL с GC, идемпотентность третьего создающего вызова. Все ТРИ сценария строки 216 предъявлены живьём (подпись банка · halt на потолке с exit 4 · `409 run_not_resumable/ceiling_reached` и лечение новым прогоном). > ⛔ **ПЯТЬ ПОТОКОВ РАБОТЫ — состояние на 07.09 (пере-снято лендингами смены №23).** > **(1) ПОЛИГОН — «ремонт прибора»** (`docs/POLYGON_INSTRUMENT_REPAIR_SESSION_PROMPT.md`, строки 319 · 300 · 265 · 143): > ⚠ **ЕДИНСТВЕННЫЙ ПОТОК, НЕ СДВИНУВШИЙСЯ ЗА СМЕНУ — сессия по промту так и не стартовала.** Блокирующая линза @@ -13,7 +13,8 @@ > `3d90943`). Строки 285 · 305 · 325 закрыты. ⚠ **Синхронный разрез приёма принят ПОСТРОЕННЫМ, НО НЕ ГОТОВЫМ** — > сработало правило остановки (9 major из 11 в одном механизме), десять пунктов несёт строка **332**; ложный > инвариант бюджета заландён осознанно (окно лжи 8 секунд при дефолте в 16 минутах, ломается идемпотентность). -> **(10) ПАК «ВЫЗОВ, КОТОРЫЙ ОБОРВАЛИ МЫ» — ✅ ПРИНЯТ И ЗАЛЕНДЕН 10.09, акт `D39.232`** (`3f05fab`, 28 путей, $0). Движок перестал платить дважды за одну генерацию. Числа пере-сняты оркестратором: батарея 19 ok · 0 FAIL · 4 скипа; каталог 276 → 360, батарея 65 → 149; тесты +67 / −0; снапшот не двинулся. ⛔ Приёмка ДО ратификации нашла пять выживших мутаций, все на носителях ВИДИМОСТИ денег — то есть на условии слова владельца. ⭐ Куплены четыре нормы об измерении и три класса вырожденных пинов. Остатки — строки **374**–**377**. +> **(11) ДВА СТОПА — СЛОВО ВЛАДЕЛЬЦА НАКОНЕЦ ЗАПИСАНО, акт `D39.233`.** У человека одна кнопка, и она рвёт всё в полёте; мягкий глагол в движке построен, но дёрнуть его может только денежный потолок. ⛔ Контракт и платформа УЖЕ обещают мягкий стоп, которого нет, а платформенный грейс (600 с) меньше законного ожидания вызова (1240 с). ⛔ Пометка «это оценка» — условие слова владельца — по шву НЕ едет и потребителя не имеет (строка **382**). ✅ Замером закрыты строки **374** (вердикт главы остановка уже не портит) и **371**. Пак не заказан: ждёт слова владельца по форме (рекомендация — «повтор ужесточает», коды выхода не трогать). +> **(10) ПАК «ВЫЗОВ, КОТОРЫЙ ОБОРВАЛИ МЫ» — ✅ ПРИНЯТ И ЗАЛЕНДЕН 10.09, акт `D39.232`** (`3f05fab`, 28 путей, $0). Движок перестал платить дважды за одну генерацию. Числа пере-сняты оркестратором: батарея 19 ok · 0 FAIL · 4 скипа; каталог 276 → 360, батарея 65 → 149; тесты +67 / −0; снапшот не двинулся. ⛔ Приёмка ДО ратификации нашла пять выживших мутаций, все на носителях ВИДИМОСТИ денег — то есть на условии слова владельца. ⭐ Куплены четыре нормы об измерении и три класса вырожденных пинов. Остатки — строки **375**–**377** (**374** закрыта замером, акт `D39.233` п.2). > **(9) ТРАНСПОРТ: КОНСИЛИУМ СОШЁЛСЯ, БИТ ВЛАДЕЛЬЦА ЗАКРЫТ — акт `D39.230`.** Оборванный НАМИ вызов: списывается по оценке с читателя, **пометка — УСЛОВИЕ решения**; платная проба отклонена (вопрос к вендору осознанно открыт); ожидание до ~20 мин разрешено ⇒ дедлайн от бюджета. ⛔ Попутно вскрыто: наш `research/21` §Q6 неверен ДВАЖДЫ и обе половины держали неверный дедлайн полтора месяца (эррата в его шапке); ось эскалации D2 фиктивна (строка **369**); нулевая ветвь не пишет чекпойнт ⇒ резюм ПЕРЕКУПАЕТ вызов. ⇒ **следующий движковый пак — строка 360**, пишется целиком. > **(8) ПЛАТФОРМЕННЫЙ ПАК «РАЗРЕЗ ПРИЁМА И ПРАВДА О СЕБЕ» — ✅ ПРИНЯТ И ЗАЛЕНДЕН 08.09, акт `D39.229`** (`ddcbf0c`, 26 путей, $0). Синхронный вход разреза под потолком (`x/sync/semaphore`, число из `jobs.DefaultWorkers`); граница хвоста выведена из кода; регистр приведён к правде — открытых 112 → **109**, major 3 → **1**. ⭐ Потолок сам назвал, чего НЕ ограничивает: материализатор и рекoнсайлер порождают процессы мимо него — строка **364**. Адверсариальный проход: 8 находок, 6 — дефекты этого же пака на ЗЕЛЁНОЙ батарее. > **(7) ДВИЖКОВЫЙ ПАК «ДВИЖОК УЖЕ ЗНАЕТ» — ✅ ПРИНЯТ С ДВУМЯ ДОФИКСАМИ И ЗАЛЕНДЕН 08.09, акт `D39.228`** (`2b6f1f4`, 21 путь). Закрыты строки 355 · 357 · 358; у **253** и **312** закрыта только ДВИЖКОВАЯ половина — платформенная жива и ждёт читателя (испр. 08.09 по кросс-модельному ревью ратификаций). Ратифицировано: аддитивное поле манифеста ключ НЕ двигает, но обязано быть отличимо от своего нуля. Третья смена поведения заказана приёмкой — лог и экран перестали спорить о полноте банка. Не взятое получило носители: **362** (ремонт против ремонта, 77 % денег чанка) и **363** (текстовый сайдкар молчит о неполноте). Батарея 19 ok · 0 FAIL · `EXIT=0`, каталог мутаций 259 → 276, тесты +17 / −0 — все числа пере-сняты оркестратором. @@ -383,7 +384,7 @@ Ни батарея, ни «0 выживших» прошлого прогона этого не показывали — сказал только сам гейт, когда мутация пережила посадку. Это второй раз за смену, когда `SURVIVED` оказался вердиктом о ПИНЕ, а не о коде. -⭐ **И ратифицированный сдвиг самой границы, снявший переплату читателя (строка 371).** Диагноз автора +⭐ **И ратифицированный сдвиг самой границы, снявший переплату читателя (строка 371 — ✅ ЗАКРЫТА актом `D39.233` п.3 10.09).** Диагноз автора конструкции: принцип верен, неверен СЛОЙ ЗАМЕРА. `WroteRequest` — это TCP-окно пира, о приложении за ним он не говорит ничего (LB принял, бэкенд не увидел — ровно мои 3 из 25). ⇒ **два предиката, две границы:** ДЕНЬГИ рисуются на подтверждении провайдера (2xx-объект у нас в руках), РЕТРАЙ — по-прежнему на записи. diff --git a/docs/architecture/05-decisions-index.md b/docs/architecture/05-decisions-index.md index d9f3d693..d26163f0 100644 --- a/docs/architecture/05-decisions-index.md +++ b/docs/architecture/05-decisions-index.md @@ -1,4 +1,4 @@ -# Реестр D-нот — карта актуальности v2 (D1–D39.232; титул — носитель головы, бампать при каждом аппенде) +# Реестр D-нот — карта актуальности v2 (D1–D39.233; титул — носитель головы, бампать при каждом аппенде) > ⚠ **Колонку «тело» `counts.py --check` НЕ сторожит по устройству:** он сверяет полноту НОМЕРОВ, а не > место тела, поэтому колонка держится дисциплиной лендинга. Не нашёл тело по колонке — иди в слайсы, @@ -291,3 +291,4 @@ | D39.230 | 08.09 | **СЛОВО ВЛАДЕЛЬЦА по оборванным вызовам** (по итогу консилиума с чистым Fable 5, строка 360): форма **А** — оценка списывается с читателя, но **пометка (`attempt_timeout` + `estimated=1` + публикация счёта и суммы оценочных строк рядом с `committed_usd`) есть УСЛОВИЕ решения**, не опция; платная проба баланса ОТКЛОНЕНА ⇒ вопрос «биллит ли вендор оборванное» осознанно остаётся открытым; ожидание провайдера до ~20 мин РАЗРЕШЕНО ⇒ дедлайн считается от бюджета. Бит закрыт, пак пишется целиком | деньги · транспорт · слово владельца | | D39.231 | 10.09 | **ДИСПОЗИЦИИ, РОЗДАННЫЕ ПИСЬМАМИ, РАТИФИЦИРОВАНЫ ЗАДНИМ ЧИСЛОМ.** Три ответа оркестратора движковой сессии жили только в межсессионных сообщениях; сессия упала, tmpfs-канал умер — слово перестало существовать. ⇒ **норма: диспозиция ратифицируется ИЛИ получает строку ТЕМ ЖЕ движением, каким отправляется ответ.** Ратифицировано удаление h2-write-бонда; два продуктовых вопроса заведены строками **374**/**375** вместо обещания; откат разведения предикатов отклонён уликой ($0.80 за отклонённый провайдером запрос) | процесс · транспорт · деньги | | D39.232 | 10.09 | **АКТ: движковый пак «вызов, который оборвали мы» ПРИНЯТ** (`3f05fab`, 28 путей, $0). Движок перестал платить дважды за одну генерацию: граница денег — ДОСТАВКА, дедлайн считается от бюджета, оплаченный обрыв не перепокупается. Закрыты 360 · 369 · 78. ⛔ Приёмка двумя рубежами ДО ратификации нашла 5 блокеров: **пять мутаций выжили на зелёной батарее, все на носителях ВИДИМОСТИ денег** — то есть на УСЛОВИИ слова владельца. ⭐ Куплены четыре нормы об измерении (знаменатель по одному глаголу · глаголы по имени · знаменатель закрывает одну ось) и три класса вырожденных пинов | приёмка · транспорт · деньги · метод | +| D39.233 | 10.09 | **ДВА СТОПА получают носитель; две строки закрыты замером; условие слова владельца — без потребителя.** У человека одна кнопка и она ЖЁСТКАЯ (`cmd/tmctl/main.go:207`); мягкий глагол построен, но локален волне и дёргает его только потолок (`waverun.go:353`); второй Ctrl-C НЕ ДЕЛАЕТ НИЧЕГО. ⛔ Контракт (`openapi.yaml:762`) и платформа (`runner.go:56-58`) уже ОБЕЩАЮТ мягкий стоп, которого нет, а `stopGrace` 600 с меньше потолка ожидания 1240 с. ⛔ Пометка «оценка» по шву НЕ едет (0 хитов в кадрах, контроль 6) ⇒ `D39.230` п.1 без потребителя — строка **382**. ✅ Закрыты **374** (вердикт главы остановка уже не портит — пере-снято, пин зелён) и **371** (переплата невозможна: `Billable` требует 2xx). Рекомендована форма «повтор ужесточает», коды выхода не трогать | стоп · деньги · шов · метод | diff --git a/docs/architecture/05-decisions-log.md b/docs/architecture/05-decisions-log.md index 7da5f6b9..1145ee2b 100644 --- a/docs/architecture/05-decisions-log.md +++ b/docs/architecture/05-decisions-log.md @@ -1,11 +1,11 @@ -# Журнал решений оркестратора — контракт D1–D39.232 (живой файл: карта · эрраты · живые тела · голова D39.124+ (подрезка D39.139); тела закрытых эр — в слайсах `docs/archive/architecture/`, указатель ниже; реестр всех нот — `05-decisions-index.md`) +# Журнал решений оркестратора — контракт D1–D39.233 (живой файл: карта · эрраты · живые тела · голова D39.124+ (подрезка D39.139); тела закрытых эр — в слайсах `docs/archive/architecture/`, указатель ниже; реестр всех нот — `05-decisions-index.md`) > **⟶ КАРТА АКТУАЛЬНОСТИ (ревизия D31, продлена до D38.2 [12.07]; исторические записи ниже НЕ переписываются — дисциплина D23.3).** Работая с контрактом (греп номера: живой файл → слайсы, целиком НЕ читать — D39.125), держи под рукой, что чем перекрыто: > ⚠ **Эррата 09.08 (D39.125):** D39.111 п.1 предписывал промту S3 «максимум = баланс МИНУС открытые холды» — формула ОШИБОЧНА (вычитание дважды), исправлена D39.115 п.2(а): максимум = Balance КАК ЕСТЬ; тело D39.111 — в слайсе `../archive/architecture/05-decisions-D39-106-123.md` (испр. 05.09: прежнее «живёт ниже в этом файле» протухло подрезкой D39.139) (голова D39.106+). > ⚠ **Эррата 15.08 (D39.132):** D39.131 п.2(а) перечисляет полосу отказов «10 конфиг · 11 источник · 12 лок · 19 безымянный» — читать через D39.132 п.2(г): полоса ДОПОЛНЕНА **exit 13 = `schema_mismatch`** (~~обе зоны сошлись на числе независимо~~; ратифицирован направлением, ФИНАЛИЗИРОВАН приёмкой D39.134). > ⚠ **Эррата 15.08-б (D39.134 п.2а, вписана по аудиту корпуса):** клейм «зоны сошлись на 13 НЕЗАВИСИМО» (строкой выше и в D39.132 п.2г) СНЯТ — платформа прочитала число из незакоммиченного дерева движка и сама это записала (`platform/internal/ingest/exit.go:63-64`); на силу ратификации не влияет. > ⚠ **Эррата 15.08-в (D39.136 п.5):** форма контракт-ревью «фазный воркфлоу оркестратора» ОТМЕНЕНА словом владельца тем же днём — исполняет ОТДЕЛЬНАЯ СЕССИЯ со своим онбордингом (норма D39.120 п.2; оркестратор — автор части ратификаций 0.2.3, author≠reviewer); запущенная воркфлоу-фаза 1 остановлена, её результаты выброшены; промт — `docs/CONTRACT_REVIEW_SESSION_PROMPT.md`. -> ⚠ **Эррата 10.09-а (`D39.232` п.9) — ПЕРЕЧЕНЬ ОСТАТКОВ НЕПОЛОН: их не четыре, а семь.** Пункт 9 писал «остатки заведены строками **374**–**377**» — это было верно в момент акта и перестало быть в тот же день: три находки приёмки, не вошедшие в круг, получили строки **378** (пин «не-2xx — не покупка» ловит мутанта ассертом об исходе прогона, денежные проверки не исполняются), **379** (два пина не утверждают своей посылки) и **380** (у денежного утверждения про бюджет пере-делки один носитель, а не пять). Две из трёх пере-сняты моим прибором на копии — с контрольной величиной рядом с нулём. Тело ноты не переписывается (D23.3); читать п.9 как **374**–**380**. +> ⚠ **Эррата 10.09-а (`D39.232` п.9) — ПЕРЕЧЕНЬ ОСТАТКОВ НЕПОЛОН: их не четыре, а семь.** Пункт 9 писал «остатки заведены строками **374**–**377**» — это было верно в момент акта и перестало быть в тот же день: три находки приёмки, не вошедшие в круг, получили строки **378** (пин «не-2xx — не покупка» ловит мутанта ассертом об исходе прогона, денежные проверки не исполняются), **379** (два пина не утверждают своей посылки) и **380** (у денежного утверждения про бюджет пере-делки один носитель, а не пять). Две из трёх пере-сняты моим прибором на копии — с контрольной величиной рядом с нулём. Тело ноты не переписывается (D23.3); читать п.9 как **374**–**380**. ⚠ И ещё раз в тот же день: **374** и **371** ЗАКРЫТЫ замером (акт `D39.233` п.2 и п.3) ⇒ живой остаток темы — список в `D39.233` п.8, а не здесь. > ⛔⛔ **Эррата 08.09-д (`D39.229` п.2) — «ЧИСЛА ПЕРЕ-СНЯТЫ МНОЮ» ДОКАЗЫВАЛИ ПОЧТИ НИЧЕГО О ПРЕДМЕТЕ ПАКА, И ЭТО КЛАСС `D39.202`, ПРИМЕНЁННЫЙ К САМОЙ БАТАРЕЕ.** Пункт 2 записывает: «`go test ./... -count=1` → 20 пакетов `ok` · 0 FAIL · EXIT=0». Ровно эту картинку печатает машина БЕЗ Postgres, потому что `go test` без `-v` скипы не показывает вовсе. Замер в моём окружении: `TM_PLATFORM_TEST_DSN` пуст, прогон даёт **431 скип**, и среди них — **все десять тестов `internal/books/limit_test.go`**, то есть весь пин ЦЕНТРАЛЬНОГО механизма пака. `go test ./internal/books/ -run TestTheHostRunsNoMoreCutsAtOnceThanItsCapAllows -v` в том же окружении печатает `SKIP` и следом `PASS`. ⇒ мой приёмочный прогон потолка не касался ни разу. **Нашёл охотник вне карты, и доказал не рассуждением, а двусторонней посадкой:** ослабленный семафор (`NewWeighted(limit+3)`) без DSN даёт `ok 0.009s`, с DSN — `FAIL: TestTheHostRunsNoMoreCutsAtOnceThanItsCapAllows`. ⭐ **Пробел закрыт исполнением, а не оговоркой:** я подняла стенд (сокет `/tmp/.s.PGSQL.5432`) и прогнала оба флагманских теста с живой базой — **PASS, 0.81 с и 2.45 с**, а затем и всю платформенную батарею со стендом: **20 пакетов `ok` · 0 FAIL · скипов 9 вместо 431** (остаток гейтится движковым бинарём и `pg_restore`) — число независимо совпало со счётом слепого верификатора. Механизм ЗАПИНЕН по-настоящему; неверен был мой прибор, а не работа зоны. ⇒ **НОРМА: приёмочный прогон печатает счёт СКИПОВ рядом с числом `ok`, иначе «пере-снято мною» и «пере-снято на четырёхстах невыполненных тестах» неразличимы.** Гейт зоны (`make check`) скипы печатает и пустой лог объявляет красным — а голый `go test`, которым мерила приёмка, слеп по построению. ⚠ И `D39.228` п.2 (движковая батарея) той же поправке НЕ подлежит: там гейтов хоста нет, скипов 4 и они названы. > ⚠⚠ **Эррата 08.09-г (`D39.228` п.1 и п.3) — ТРИ ОШИБКИ ОДНОГО АКТА, НАЙДЕНЫ КРОСС-МОДЕЛЬНЫМ РЕВЬЮ РАТИФИКАЦИЙ, которое норма велела провести ДО ратификации, а я провела ПОСЛЕ.** **(а) ⛔ ЦЕНА БАМПА НАЗВАНА ДЕНЬГАМИ, КОТОРЫХ ТАМ НЕТ.** Пункт 3 пишет, что бамп `manifestVersion` «выбрасывает каждый сохранённый сайдкар и ПЕРЕКУПАЕТ каждую книгу». Код говорит другое и говорит это четырежды (`manifest.go:78`, `:99`, `:118`, `:729`): «discard every stored sidecar and **re-cut** / **re-chunk** every book». Ре-чанк — процессор, не деньги: единственный денежный читатель манифеста (`rebill.go`) на нулевом манифесте не объявляет ряд перепокупкой, а делает дорогую сверку контент-хешей и при совпадении продолжает как `$0 resume`. Настоящая цена бампа — пере-нарезка на каждом чтении, одна дорогая проверка в гейте согласия и **релиз платформы В НОГУ** (`KnownManifestVersion`), и последний довод СИЛЬНЕЕ денежного, но в ноте не назван. ⇒ **правило пункта 3 остаётся в силе, довод под ним заменён:** запрет бампа держится на двузонном релизе, а не на перепокупке. ⚠ Следующий, кто станет взвешивать бамп, взвесил бы не ту цену. **(б) ⛔ СТРОКА 312 ЗАКРЫТА НАПОЛОВИНУ И УДАЛЕНА ЦЕЛИКОМ — тот же класс, что 253, в том же акте решённый ПРОТИВОПОЛОЖНО.** Живым остатком 312 был Д-3 — «увидит ли ноту ЧЕЛОВЕК»; после лендинга поле `TOCUnreadable` пишется в манифест и не читается никем: **0 хитов `toc_unreadable` в `platform/` при 200 `.go`**. Факт переехал из лога в файл, которого никто не открывает, и для пользователя не изменилось ничего. Строка ВОЗВРАЩЕНА суженной до платформенной половины; класс A для этого поля объявлен там же (акт объявлял его только для `consolidation`). **(в) ⛔ ЭРРАТА 08.09-в ЗАКРЫЛА 1 НОСИТЕЛЬ ИЗ 6.** «253 закрыта» стояло ещё в `05-decisions-index.md`, `docs/README.md`, CURRENT-STATE `docs/PROGRESS.md` и баннере архивного промта — а следующая сессия онбордится с CURRENT-STATE, а не с шапки журнала. Приведены к правде вместе с 312. ⚠ Это МОЯ ЖЕ норма «класс закрыт только со ЗНАМЕНАТЕЛЕМ N из M», применённая к моей же эррате и не исполненная. **(г) Атрибуция коммита.** `f1404f0` (акт ПЛАТФОРМЫ) унёс закрытие ДВИЖКОВЫХ рядов 312/355/357/358 и сужение 253; движковый акт `e9a0f78` бэклога не трогал вовсе. Историю не переписываю — читателю `git log -- docs/BACKLOG.md` объясняет эта строка. > ⚠ **Эррата 08.09-в (`D39.228` п.1) — АКТ ОБЪЯВИЛ ЗАКРЫТОЙ СТРОКУ, У КОТОРОЙ ЗАКРЫТА ПОЛОВИНА.** Пункт 1 писал «закрыты строки 253 · 355 · 357 · 358». Для 253 это неверно: её собственное тело называет лечением (б) «полнота консолидации в сайдкар **+ поле на `BankPage`**», и второй половины нет — у секции `consolidation` ноль читателей, класс A заведён сознательно. ⇒ строка СУЖЕНА до платформенной половины, не удалена; 355 · 357 · 358 · 312 закрыты и удалены. ⛔ **Класс `D39.167` п.1 в чистом виде, и поймала его та же норма, что его называет:** «каждое утверждение акта о ряде сверяется его СТАТУСОМ в тот же заход» — сверка сделана через час после акта, а не до, и это разрыв в МОЁМ порядке: ратификация и сверка носителей обязаны идти ОДНИМ движением, иначе акт живёт с переоценкой ровно столько, сколько я собираюсь до неё дойти. @@ -3255,3 +3255,23 @@ bought NOTHING». ⇒ **`tmctl manifest` есть НИЖНЯЯ граница, **8. Что зона сделала лучше заказа.** Починила ВОПРОС, а не три ответа (одно определение `paidAfterBurns` против трёх «правильно шагающих» площадок — `D39.216` п.3б, которого я в заказе не применил). **Сняла** механизм вместо добавления, записав ПОЧЕМУ его нет, чтобы следующий не восстановил как «нужную полноту». Назвала три собственные починки неверными до того, как их нашла приёмка. И вела самопроверку прибором, читающим исходы ИЗ САМОГО наряда: 36 позиций, исходов не из трёх — 0, «сделано» без предъявления — 0. **9. Границы вердикта — честно.** Полный мутационный прогон — число зоны (п.2). Живого платного прогона не было и не требовалось. Расхождение приёмки по одному пину не сведено: линза видит тихую зелень 15–30 % под нагрузкой, слепой на спокойной машине 0 из 8 — обе цифры названы, ни одна не выбрана. Остатки заведены строками **374**–**377**; два продуктовых вопроса (вердикт главы, семантика выгрузки) остаются владельцу. + +## D39.233 — ДВА СТОПА: слово владельца получает носитель; попутно ДВЕ строки закрыты замером, а условие `D39.230` п.1 оказалось без потребителя (10.09, оркестратор №23) ✅ + +**1. Почему нота.** Владелец 09–10.09 сказал: «должно быть два стопа, один жёсткий… и второй мягкий, который не обрывает всё сразу, чтоб деньги зазря не списывались». Слово носителя не получило — ни ноты, ни строки, ни заказа в паке — и умерло вместе с сессией, ровно как три диспозиции в `D39.231` п.1. Здесь оно записано, вместе с замером состояния и рекомендованной формой; сам пак не заказан — ждёт слова владельца по форме. Носитель работы — строка **381**. + +**2. ⛔ Я ПОСТАВИЛ ВЛАДЕЛЬЦУ ПРОДУКТОВЫЙ ВОПРОС, КОТОРОГО НЕТ: вердикт главы остановка уже НЕ портит.** Строка **374** утверждала обратное, я прочитал строку и понёс её владельцу вместо замера. Пере-снято моим прибором: паспорт главы берёт состояние единицы из `resolveChunkState` (`backend/internal/pipeline/status.go:681`), а тот пропускает `cancelled` предикатом `resolvedForResume` (`status.go:576`, `cutcall.go:253`) ⇒ остановленная позиция единицу не флагует, а `UnitsFlagged` — единственный вход вердикта (`status.go:740`). Пин `TestAStoppedPositionIsNotADecidedUnit` на дереве ЗЕЛЁН. ⇒ **строка 374 ЗАКРЫТА**, обе её половины. ⭐ Урок ровно тот, что смена уже купила и не применила: **строка трекера — это утверждение ПРОШЛОГО замера; прежде чем нести её владельцу, спрашивают дерево.** + +**3. Строка 371 ЗАКРЫТА ссылкой на 376.** Она боялась ПЕРЕПЛАТЫ: «байты ушли в наш сокет» ≠ «провайдер принял». Дерево так больше не считает: `Billable = answered && afterHeaders()` (`backend/internal/llm/attemptcut.go:221`), а `answered=true` приходит ровно из одного места — `httpllm.go:642`, ветвь «2xx, чьё тело не прочлось». ⇒ один `WroteRequest` денег не книжит НИКОГДА, и комментарий предиката называет тот самый замер, на котором строка стояла (3 из 25). Остаток — противоположного знака (недосчёт) и уже несёт строку **376**. + +**4. Замер состояния «двух стопов» (мой + движковой зоны + чистый Fable 5, три прибора, сходятся).** Кнопка одна и ЖЁСТКАЯ: `backend/cmd/tmctl/main.go:207` отменяет контекст прогона. Мягкий ГЛАГОЛ построен и работает — `latched` (`backend/internal/pipeline/waverun.go:353`, «a different verb from cancel()»), но дёргает его только денежный потолок и он ЛОКАЛЕН одному `runWave` (живых вызовов два) ⇒ нужен прогонный источник, а не новый механизм. ⛔ **Второй Ctrl-C сегодня НЕ ДЕЛАЕТ НИЧЕГО**: горутина `NotifyContext` после первого сигнала вышла, регистрация снимается лишь `defer stop()` (`main.go:208`), второй сигнал падает в буфер ёмкостью 1 и никем не читается. + +**5. ⛔ ДВА НОСИТЕЛЯ УЖЕ ОБЕЩАЮТ МЯГКИЙ СТОП, КОТОРОГО НЕТ.** **(а)** Контракт: `docs/architecture/14-api-contract/openapi.yaml:762` — «The product "stop" action; finished work is kept and not paid for again». Жёсткий стоп это нарушает: летящая единица книжится по оценке и пере-делается на резюме (`cutcall.go:208`), то есть покупается дважды. **(б)** Платформа: `platform/internal/runner/runner.go:56-58` — «the engine … finishes the in-flight chunk before exiting», `:163-164` — «SIGTERM to the engine alone so it can finish the chunk it is paying for». Оба утверждения о движке сегодня ЛОЖНЫ. И третье, числовое: `stopGrace = 10 * time.Minute` (`runner.go:59`) МЕНЬШЕ потолка ожидания вызова (`attempt_max_s` 1240 с, `backend/configs/models.yaml`) ⇒ как только SIGTERM станет мягким, systemd будет убивать ЗАКОННОЕ ожидание. Это не косметика: SIGKILL не оставляет ни пометки, ни сеттла. + +**6. Рекомендованная форма (не ратифицирована — вопрос владельцу).** Классика даёт три формы: разные сигналы (nginx `QUIT`, GitLab Runner `SIGQUIT`, Argo `stop`/`terminate`), повтор ужесточает (Celery: первый `INT` — тёплое завершение, следующий — холодное; GitLab «use twice to exit now»), эскалация супервизором по грейсу (systemd `TimeoutStopSec` → SIGKILL, Kubernetes 30 с). Рекомендуется **повтор ужесточает**, и довод не вкусовой: у платформы ОДИН глагол — `systemctl stop` = один SIGTERM (`runner.go:191`), и её же комментарии уже ждут мягкого. ⇒ первый сигнал — мягкий, второй — сегодняшний жёсткий, третий — дефолт процесса. **Коды выхода НЕ трогать: оба стопа — 5** (полоса заморожена, `main.go:118`; ни одна из изученных систем soft/hard в код выхода не кодирует), различение едет ПРИЗНАКОМ в кадре `finished`. Потолок ожидания ограничивается отказом на гейте резервации (`stagerun.go`, пер-вызовная `Reserve`): ждём одну летящую попытку (≤ ~20 мин), а не всю ретрай-цепочку (~64 мин). + +**7. ⛔ УСЛОВИЕ `D39.230` п.1 ИСПОЛНЕНО НА ОДНОМ КАНАЛЕ ИЗ ДВУХ И НЕ ИМЕЕТ ПОТРЕБИТЕЛЯ.** Владелец разрешил списывать по оценке ПРИ УСЛОВИИ пометки. Пометка живёт в `status --json` (`backend/internal/pipeline/status.go:321` `estimated_rows`/`estimated_usd`) — и НЕ едет по шву: в кадрах событий `backend/internal/runevents/runevents.go` слова `estimated` **0 хитов** (контроль: `committed` в том же файле — 6, денежный кадр несёт один `committed_micro_usd`, `:244`). Платформа её не читает: в `platform/internal` вне тестов **2 хита**, оба — КОММЕНТАРИИ, причём один прямо просит эту величину (`pgstore/credits.go:235`: «publish the count and sum of estimated-price rows beside committed_usd») (контроль: `committed_micro` — 1 хит; go-файлов вне тестов прибор прочёл 81). ⇒ читателя, ради которого пометка ставилась, у неё сегодня нет. Носитель — строка **382**. + +**8. Что осталось у темы «оборванных вызовов».** Живые строки: **375** (правка меняет ложь на молчание — нужно третье состояние), **377** (оплаченный обрыв + удачный ретрай = $0 и ни следа; ломает сверку инвойса, на которой стоит `D39.230` п.2 — ставить первой из денежных), **376**, **373**, **372** (решается вместе с мягким стопом: у гейта появляется третья причина аборта), **378**–**380** (качество пинов), **381**, **382**. Тема закрывается паком мягкого стопа плюс **377**, не раньше. + +**9. Границы.** Пункты 2 и 3 пере-сняты мной на дереве. Пункт 4 сходится у трёх приборов независимо. Вендорские формы п.6 читаны Fable 5 по официальным страницам сегодня (Celery, nginx, GitLab Runner, Sidekiq, systemd.kill, Kubernetes, Argo, Temporal); Erlang, Docker, Slurm и «Ctrl-C дважды» в Claude Code он назвал памятью, не документом, — так и записано. Потолки ожидания п.6 — арифметика по конфигу, не прогон. Живого прогона с провайдером не было и не требовалось, $0.