From 693da70ffd0e0f58c1dbcbb976a4e07e1c94842c Mon Sep 17 00:00:00 2001 From: heaven Date: Wed, 16 Sep 2026 21:53:27 +0300 Subject: [PATCH] Close nine more decisions-log anchors: six tokens and three moved onto the lines their prose names --- docs/architecture/05-decisions-log.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/architecture/05-decisions-log.md b/docs/architecture/05-decisions-log.md index 3f97d971..aa7cbaf6 100644 --- a/docs/architecture/05-decisions-log.md +++ b/docs/architecture/05-decisions-log.md @@ -986,7 +986,7 @@ **Р3 — архитектурный СТОП, и он назван, а не заметён (`PD-410`).** Проверено мной по коду и жёстче её формулировки: у движка НЕТ флага числа глав вовсе — `TranslateArgs` несёт только `--ceiling-usd` (`platform/internal/runner/engine.go:90-98`), а сам флаг каппит КУМУЛЯТИВНУЮ трату по книге -(`backend/cmd/tmctl/invocation.go:129`), тогда как волна идёт по всем чанкам. Платформа продаёт +(`backend/cmd/tmctl/invocation.go:142`=`fs.Float64("ceiling-usd"` — ⚠ испр. 16.09: было :129, там установка вывода флагов), тогда как волна идёт по всем чанкам. Платформа продаёт ГЛАВЫ, движок останавливается по ДЕНЬГАМ: единицы разные, и никакая формула полосы их не мирит, пока платёжная модель и план работ не согласованы. **Канон я НЕ смягчил** — по прецеденту §3.3 бэкенд-пака: обещание верное, дефект в реализации; смягчение канона под текущий деплой — это то, как обещание @@ -1188,7 +1188,7 @@ device». Починено `TestMain`, проверено двумя прого требованию владельца (28.08) сессия провела ШИРОКИЙ адверсариальный проход по своей готовой работе: 6 линз, 42 находки, **три фатальные**, все доказаны исполнением на настоящем движке. 1. **Мина, объявленная обезвреженной, СТОЯЛА.** `bank-apply` пишет только ФАЙЛЫ решений, а `status` - считает ре-билл от СОХРАНЁННОГО глоссария (`backend/internal/pipeline/status.go:733-744`, чей + считает ре-билл от СОХРАНЁННОГО глоссария (`backend/internal/pipeline/status.go:733-744`=`Applied repairs (pack-16)`, чей собственный комментарий это и говорит) — свёртка происходит внутри СЛЕДУЮЩЕГО `translate`. Значит сразу после правки движок отвечает «ничего не двигалось», факт не взводился, флаги не выдавались. Живой тест сессии дёргал движковый гард НАПРЯМУЮ и платформенную цепь не покрывал — класс, о @@ -2999,7 +2999,7 @@ mined-посадки (её поверхность есть подстрока у - **Подавлять повторный репорт пары на ре-сиде — НЕТ.** Это разные СОСТОЯНИЯ одного термина: «роль расходится с банком» до посадки строки и «банк держит обе» после; две строки на проводе означает только второе. Резать надо было бы слияние, а не схлопывание. Названо в доке зоны, носитель не нужен. -- **`bookfile/model.go:59` разросся до 183 строк — НЕ дефект.** Размер сам по себе им не является; строка +- **`bookfile/model.go:59`=`before the reader learned to fold an uncovered leading doc` разросся до 183 строк — НЕ дефект.** Размер сам по себе им не является; строка бэклога здесь была бы трекерной инерцией. Вернётся с ценой, если заблокирует правку. **4. Находка, оказавшаяся ПОЛОВИНЧАТОЙ, и это важнее её самой.** Платформа сообщила, что §2.12 компаньона @@ -3338,17 +3338,17 @@ bought NOTHING». ⇒ **`tmctl manifest` есть НИЖНЯЯ граница, **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 ЗАКРЫТА**, обе её половины. ⭐ Урок ровно тот, что смена уже купила и не применила: **строка трекера — это утверждение ПРОШЛОГО замера; прежде чем нести её владельцу, спрашивают дерево.** +**2. ⛔ Я ПОСТАВИЛ ВЛАДЕЛЬЦУ ПРОДУКТОВЫЙ ВОПРОС, КОТОРОГО НЕТ: вердикт главы остановка уже НЕ портит.** Строка **374** утверждала обратное, я прочитал строку и понёс её владельцу вместо замера. Пере-снято моим прибором: паспорт главы берёт состояние единицы из `resolveChunkState` (`backend/internal/pipeline/status.go:712`=`res := resolveChunkState(rows, expected)` — ⚠ испр. 16.09: было :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**. +**3. Строка 371 ЗАКРЫТА ссылкой на 376.** Она боялась ПЕРЕПЛАТЫ: «байты ушли в наш сокет» ≠ «провайдер принял». Дерево так больше не считает: `Billable = answered && afterHeaders()` (`backend/internal/llm/attemptcut.go:221`=`Billable: answered && tr.afterHeaders()`), а `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 и никем не читается. +**4. Замер состояния «двух стопов» (мой + движковой зоны + чистый Fable 5, три прибора, сходятся).** Кнопка одна и ЖЁСТКАЯ: `backend/cmd/tmctl/main.go:208`=`signal.NotifyContext(context.Background()` — ⚠ испр. 16.09: было :207 (пустая строка) отменяет контекст прогона. Мягкий ГЛАГОЛ построен и работает — `latched` (`backend/internal/pipeline/waverun.go:353`=`latched is the SPEND CEILING`, «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**. +**7. ⛔ УСЛОВИЕ `D39.230` п.1 ИСПОЛНЕНО НА ОДНОМ КАНАЛЕ ИЗ ДВУХ И НЕ ИМЕЕТ ПОТРЕБИТЕЛЯ.** Владелец разрешил списывать по оценке ПРИ УСЛОВИИ пометки. Пометка живёт в `status --json` (`backend/internal/pipeline/status.go:321`=`UnsignedBankTerms int` `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**, не раньше. @@ -3376,7 +3376,7 @@ bought NOTHING». ⇒ **`tmctl manifest` есть НИЖНЯЯ граница, **(в)** `Money` на исходе `stopped` едет ВСЕГДА, а не только когда прежде сработал потолок (сегодня — только тогда: `backend/internal/pipeline/events.go`, греп `ceilingSaid`). Вопрос покупателя «что я купил» от причины остановки не зависит. **(г)** ЗАПАСНОЙ КАНАЛ — обязанность ПЛАТФОРМЫ, не движка: у прогона, убитого по грейсу, кадра `finished` нет вовсе (его отсутствие само по себе значимо), а `status --json` числа несёт всегда ⇒ платформа читает оба. -**2. ⛔ ЭРРАТА К СОБСТВЕННОМУ АКТУ: `D39.234` п.1б назвал МЕХАНИЗМ, который ратифицированных ~20 минут НЕ ДАЁТ.** Слово владельца («одна летящая попытка, ~20 минут приемлемо») в силе и не меняется. Неверна была моя скобка «отказ на пер-вызовном гейте резервации». Замер: резервация берётся ОДИН раз на `runAttempt` (`backend/internal/pipeline/stagerun.go:477`, `admit` на `:570`), а `client.Complete` (`:699`) держит под ней ВСЮ цепочку ретраев — это дословно говорит комментарий самого кода: «This timer wraps client.Complete — the WHOLE retry chain» (`stagerun.go:984`); `retryLoop` между попытками смотрит только `ctx.Err()` (`backend/internal/llm/httpllm.go:197`). ⇒ гейт ограничивает ожидание ЦЕПОЧКОЙ (~64 мин), а не попыткой (~20). Чтобы слово владельца стало правдой, транспорт обязан видеть мягкую остановку МЕЖДУ попытками и в бэкоффе — это заказано §4.2 движкового промта. ⭐ Нашёл не я: моя механика прошла в акт непроверенной, и её опроверг чтением кода старший коллега, которого я позвал ревьюить уже написанный промт. +**2. ⛔ ЭРРАТА К СОБСТВЕННОМУ АКТУ: `D39.234` п.1б назвал МЕХАНИЗМ, который ратифицированных ~20 минут НЕ ДАЁТ.** Слово владельца («одна летящая попытка, ~20 минут приемлемо») в силе и не меняется. Неверна была моя скобка «отказ на пер-вызовном гейте резервации». Замер: резервация берётся ОДИН раз на `runAttempt` (`backend/internal/pipeline/stagerun.go:477`=`finish string`, `admit` на `:570`), а `client.Complete` (`:699`) держит под ней ВСЮ цепочку ретраев — это дословно говорит комментарий самого кода: «This timer wraps client.Complete — the WHOLE retry chain» (`stagerun.go:984`); `retryLoop` между попытками смотрит только `ctx.Err()` (`backend/internal/llm/httpllm.go:197`). ⇒ гейт ограничивает ожидание ЦЕПОЧКОЙ (~64 мин), а не попыткой (~20). Чтобы слово владельца стало правдой, транспорт обязан видеть мягкую остановку МЕЖДУ попытками и в бэкоффе — это заказано §4.2 движкового промта. ⭐ Нашёл не я: моя механика прошла в акт непроверенной, и её опроверг чтением кода старший коллега, которого я позвал ревьюить уже написанный промт. **3. Два парных промта выданы.** `docs/BACKEND_SOFT_STOP_SESSION_PROMPT.md` (строки **381** · **382** · **377** · **372** · **375** · **379**а) и `docs/PLATFORM_SOFT_STOP_SESSION_PROMPT.md`. Порознь НЕ лендятся (`D39.234` п.2). Движковый идёт ОДНОЙ сессией в ДВА лендинга с заранее объявленной линией (резать на две сессии нельзя: §4.2 и §4.8 правят одну функцию `retryLoop`); кончившийся контекст после первого лендинга объявлен штатным исходом. ⚠ Обе редакции промтов правились ДО выдачи по ревью: в движковом восемь мест, в платформенном шесть — включая ДВЕ мои прямые лжи («латч не остановит банковые проходы» — они идут через тот же гейт; «`cancelled` не может затереть настоящую причину» — может, апсерт по ключу стадии ЗАМЕНЯЕТ строку).