diff --git a/platform/docs/DEFECT_REGISTER.md b/platform/docs/DEFECT_REGISTER.md index 2c55d2fe..7fa97a11 100644 --- a/platform/docs/DEFECT_REGISTER.md +++ b/platform/docs/DEFECT_REGISTER.md @@ -25,7 +25,7 @@ |---|---|---|---|---|---|---| | PD-410 | bug | **major** | `internal/pgstore/readmodel.go:460` `draftWork` (и вся модель «купленный диапазон в обеих волнах»), контраст: движку передаётся ТОЛЬКО `--ceiling-usd` (`internal/runs/spawn.go` `bookCap`), draft-волна фанится по ВСЕМ чанкам книги (`backend/internal/pipeline/waverun.go`) | **Полоса и платёжная модель считают, что прогон работает над диапазоном «C глав от edit-фронта» в обеих волнах, а движок гонит волны последовательно от СВОИХ фронтов до денег — и на continuation над недочерновленной книгой (`draft_before ≥ chapters_before + C`) формула даёт `draftWork=0`: движок тратит весь потолок на черновики, полоса стоит 0/C весь прогон со stage `editing` на прогоне, который только черновит.** Зеркало дефекта, который `draftWork` чинил (тот закрывал «ready на 50%», этот открывает «0% при сделанной работе»). Это НЕ локальная ошибка формулы: любая полоса из (баз, C, D, E) где-то соврёт, пока платёжная модель («довести до конца C глав от `e0`») и движковый план работ («деньги в порядке волн от фронтов») не согласованы — лечение требует либо передавать движку план (диапазон/волны), либо выводить total полосы из движкового плана; `ceiling_chapters` сегодня до движка не доезжает вовсе. Архитектурное, ОТДЕЛЬНЫМ паком по слову оркестратора 28.08 («остановись и скажи»); подтверждено воркфлоу арифметикой на существующем зелёном пине (`TestARunOverADraftedBacklogOwesOnlyTheLastPass` держит `draftWork=0` в родственной точке) ⚠ **ПЕРЕ-ДИСПОЗИЦИОНИРОВАНА паком P12 (30.08): направление ЕСТЬ** — D39.165 §1, четыре части (цена от объёма исходника · стоп объёма в движке · хвост качества в тариф). ⛔ **(г) константу по сегодняшним числам НЕ калибровать** — гейт строка бэклога 202. Что остаётся открытым: не решение, а МЕХАНИКА, и она — отдельный пак (слово оркестратора 28.08), паком P12 не тронута. | open | воркфлоу-ревью P9 28.08 (линза bar:double-count), стоп-решение сессии P9 28.08: тянет архитектуру, не чинится в паке | | PD-424 | bug | **major** | `internal/runs/reconcile.go` `reopen` (вердикт `deferred`) и `reconcileOne`, `internal/pgstore/runs.go` `StalledRuns`, `internal/pgstore/observe.go` | **ЖИВОЙ прогон с намертво заблокированной расплатой невидим на ВСЕХ поверхностях `PD-385` и не поддаётся `run abandon` — счётчик неудач не растёт НИКОГДА.** Цепь, воспроизведённая охотником приёмки на десяти проходах: юнит исчез без маркера → `restart` → `settle` возвращает `settlementBlocked, nil` (не ошибку) → `reopen` отказывается открыть новую попытку поверх незакрытого холда и возвращает вердикт `deferred` → `case deferred: return nil` → `reconcileOne` считает проход УСПЕШНЫМ и вдобавок зовёт `ClearRunDeferral`. Итог: `reconcile_failures` 0, `reconcile_after` NULL, `StalledRuns(5)` пуст, гейдж 0, холд заморожен, движок дёргается каждым проходом. Вывести из состояния может только пользователь, нажав Stop, — и никто ему об этом не говорит. ⚠ Это ТА ЖЕ болезнь, что `PD-384`/`PD-385`, но в ЖИВОЙ фазе, куда пак P11 не дошёл: он лечил вторую фазу (расплату) и её поверхности. ⚠ **СУЖЕНА пак P12 (30–31.08), НЕ ЗАКРЫТА — половина «невидим» вылечена, половина «ручки нет» стоит.** Приор зоны принят и исполнен: блокированная расплата — НЕУДАЧА владеющей фазы, и она считается. `restart` больше не выбрасывает вердикт `settle`: на `settlementBlocked` он возвращает `errSettlementBlocked` ДО `reopen` (прежний путь платил за запрос, чтобы узнать то, что `settle` только что сказал, писал WARN, винящий рестарт в блокировке расплаты, и отвечал `nil`, который вызывающий считал успешным проходом). `reconcileOne` ключует на этом сентинеле причину — ОДНА фраза на оба фазовых пути — и ЗАДЕРЖКУ: расписание РАСПЛАТЫ, а не живой фазы, потому что открытая резервация и есть resume-гейт пользователя, и получасовой живой бэкофф держал бы его resume за блокировку, с которой он ничего сделать не может. Вердикт `deferred` у `reopen` оставлен как был: после правки он достижим только на `settlementRaced` с моментально открытой резервацией — самоисправляется следующим проходом, и фаза расплаты прямо аргументирует, что гонка не должна попадать на счётчик оператора. Гард выключения (`ctx.Err()`) стоит: счёт, придуманный ОСТАНОВКОЙ демона, — тот самый дефект, который соседняя фаза уже нашла. Итог, проверенный пином `runs.TestALiveRunWhoseSettlementIsBlockedIsCountedAndReachesTheOperator`: `reconcile_failures` доходит до порога, строка появляется в `runs --stalled` (половина `live`, не `settling`), гейдж `tm_platform_runs_stalled` = 1, отсрочка не превышает `settlementBackoffCap`. Посадка M13 КРАСНАЯ адресно. ⚠ **ЧТО ОСТАЁТСЯ ОТКРЫТЫМ и почему не взято этим паком:** терминальная РУЧКА. `run abandon` ветвится по `runs.finished_at` и на живом прогоне уходит в живую ветку, а `AbandonRun` отказывает над попыткой, ещё называющей юнит. Ручка «процесса нет, закрой деньги» — НОВАЯ разрушительная операторская поверхность над деньгами, и её дизайн (кто вправе звать, чем доказывается отсутствие процесса, что будет, если процесс вернётся) — решение своего размера, а не хвост этой правки. Сегодня пользователь выводит прогон из состояния кнопкой Stop, и теперь об этом хотя бы говорят все три поверхности `PD-385`. ⚠ **РУЧКА ПОСТРОЕНА паком «закрыть цикл» (04.09) — строка СУЖЕНА до своего остатка, не закрыта.** Форма: та же команда `tmplatformctl run abandon`, потому что рантбук уже посылает оператора именно к ней; новой команды нет. **Кто вправе звать** — оператор из CLI и только оттуда: терминальный вердикт над деньгами на HTTP-поверхность не выходит, как и `run unquarantine`. **Чем доказывается отсутствие процесса** — систему СПРАШИВАЮТ, а не верят ей на слово: команда сама зовёт `runner.Alive` по имени юнита стоящей попытки, и ответов **ЧЕТЫРЕ**, а не два — «нет» пропускает, «есть» отказывает, **недостижимая шина отказывает тоже** (отсутствие ответа не есть отсутствие процесса), и — четвёртый, найденный адверсариальным проходом уже по готовой работе — **«это НЕ ТОТ systemd» отказывает тоже**: `systemctl --user` отвечает про менеджер СПРАШИВАЮЩЕГО, а прогоны живут у пользователя демона, и там юнит, о котором чужой менеджер не слышал, неотличим от кончившегося (замерено: `ActiveState=inactive`, выход 0). Различитель — владелец `TM_PLATFORM_STATE_DIR`, ⚠ **поэтому переменная деплоя обязана быть экспортирована в оболочке оператора, иначе команда ОТКАЖЕТ** (рантбук об этом теперь говорит). Пины: `cmd/tmplatformctl/abandon_proof_test.go` — четыре ответа различителя, три отказа и путь «gone» через саму команду, плюс отказ над ЖИВЫМ транзиентным юнитом под systemd-гейтом. Второе условие проверяет хранилище ВНУТРИ транзакции: `reconcile_failures >= abandonAfter`, тот же пол, что у settling-ветви, — видеть и иметь право уничтожить это разные разрешения. **Деньги:** попытка с базовой линией и отчитанной цифрой рассчитывается по РАЗНИЦЕ (та же арифметика, что печатает `StalledRuns`), холд закрывается как `settled` с ценой в леджере; попытку без базовой линии ценить нечем — холд возвращается ЦЕЛЫМ, довод settling-ветви, доехавший до живой половины. **Что будет, если процесс вернётся** — названо прямо, потому что это цена ручки: живой движок продолжит тратить против СВОЕГО книжного потолка, а холд аккаунта уже закрыт, значит эту трату не оплатит никто и провайдеру платит ДЕПЛОЙ. Она ограничена (потолком этого же прогона) и не эксплуатируема аккаунтом: попасть в состояние можно только через прогон, который собственный реконсилятор деплоя не смог закончить. Тем же касанием закрыта долговечная половина `PD-418`. Пины: `internal/runs/abandon_orphan_test.go` — отказ без доказательства · отказ ниже пола · вердикт `orphan` со списанием по отчёту · целый холд там, где цены нет · осиротевшая попытка ЖИВОГО прогона. ⚠ **ОСТАТОК, ради которого строка остаётся открытой:** отсутствие процесса доказывается ОДИН раз, в момент команды, и между этим ответом и коммитом транзакции есть окно, в которое юнит может подняться заново. Окно узкое, его цена названа выше, но оно есть, и закрыть его может только фактически транзакционная проба — арбитр в хранилище, а не ещё одна проверка в CLI. | open | приёмка оркестратора №19 по паку P11 (охотник вне карты, воспроизведено 10 проходами) | -| PD-440 | bug | **major** | `internal/pricing/pricing.go` `DefaultPerChapter`, `internal/runs/spawn.go` `bookCap`, `internal/pgstore/books.go` `ChaptersLeft`; движковая половина — `backend/internal/pipeline/stagerun.go` (резерв под шаг при `waves.workers` параллельных вызовах) | **КНИГУ ЧЕРЕЗ API НЕЛЬЗЯ ДОЧИТАТЬ НИКАКИМИ ДЕНЬГАМИ — покупка перестаёт покупать.** Прирост книжного потолка за прогон равен `chaptersLeft × DefaultPerChapter`, то есть максимум `chaptersLeft × $0.03`. Движок перед редакторским шагом РЕЗЕРВИРУЕТ оценку на каждый параллельный вызов (`waves.workers`, на стенде 4 × ≈$0.069 = ≈$0.28) и останавливается на ПЕРВОЙ отказанной резервации, не дожидаясь уже летящих. Как только прирост становится меньше стоимости одного шага волны, каждый следующий прогон встаёт МГНОВЕННО и не продвигает книгу ни на юнит — а прирост только УБЫВАЕТ, потому что `chaptersLeft` уменьшается с каждой дочитанной главой. ⚠ **Воспроизведено живьём дважды подряд, платным прогоном 04.09** (книга `bk_SS5VES2JELESJSTR`, 10 глав, после первой готовой главы `chaptersLeft = 9`, прирост $0.27): прогоны `run_CR2RN76NAKYKAJE5` и `run_VSXPMJE53KJQQAOS` — оба `paused/credit_exhausted` за 10–15 секунд, `committed` не сдвинулся ни на микро-доллар (278319 до и после), холд вернулся целиком. Движковая строка обоих: `book USD ceiling reached ($0.548319) (committed=$0.278319 reserved=$0.207805, denied estimate=$0.069828) … reserve ceiling reached`. Это ПРОДУКТОВЫЙ СТОП, а не «шкала короче»: занижение ставки ×4.47 (`D39.179` п.1) до сих пор читалось как неудобство, и вот порог, за которым оно становится недостижимостью результата. ⚠ **Денег сам стоп НЕ жжёт** — отменённые вызовы этих двух прогонов не успели уйти (латентность 20 и 31 мс, `model_actual` пуст, 0 токенов); неучтённый расход — отдельная строка `PD-441`. ⚠ **Чинится СОГЛАСОВАННО, одной стороной нельзя:** поднять ставку — платформенная половина и она меняет всю шкалу покупки; не бросать летящие вызовы и/или считать резерв по фактической конкуренции — движковая. Проверка без Go: `sqlite3 -header -column "file:?mode=ro" "select trace_id, count(*), round(sum(cost_usd),6) from request_log group by trace_id;"` | open | платный сквозной прогон через API, пак «закрыть цикл» 04.09 (воспроизведено дважды) | +| PD-440 | bug | **major** | `internal/pricing/pricing.go` `DefaultPerChapter`, `internal/runs/spawn.go` `bookCap`, `internal/pgstore/books.go` `ChaptersLeft`; движковая половина — `backend/internal/pipeline/stagerun.go` (резерв под шаг при `waves.workers` параллельных вызовах) | **КНИГУ ЧЕРЕЗ API НЕЛЬЗЯ ДОЧИТАТЬ НИКАКИМИ ДЕНЬГАМИ — покупка перестаёт покупать.** Прирост книжного потолка за прогон равен `chaptersLeft × DefaultPerChapter`, то есть максимум `chaptersLeft × $0.03`. Движок перед редакторским шагом РЕЗЕРВИРУЕТ оценку на каждый параллельный вызов (`waves.workers`, на стенде 4 × ≈$0.069 = ≈$0.28) и останавливается на ПЕРВОЙ отказанной резервации, не дожидаясь уже летящих. Как только прирост становится меньше стоимости одного шага волны, каждый следующий прогон встаёт МГНОВЕННО и не продвигает книгу ни на юнит — а прирост только УБЫВАЕТ, потому что `chaptersLeft` уменьшается с каждой дочитанной главой. ⚠ **Воспроизведено живьём дважды подряд, платным прогоном 04.09** (книга `bk_SS5VES2JELESJSTR`, 10 глав, после первой готовой главы `chaptersLeft = 9`, прирост $0.27): прогоны `run_CR2RN76NAKYKAJE5` и `run_VSXPMJE53KJQQAOS` — оба `paused/credit_exhausted` за 10–15 секунд, `committed` не сдвинулся ни на микро-доллар (278319 до и после), холд вернулся целиком. Движковая строка обоих: `book USD ceiling reached ($0.548319) (committed=$0.278319 reserved=$0.207805, denied estimate=$0.069828) … reserve ceiling reached`. Это ПРОДУКТОВЫЙ СТОП, а не «шкала короче»: занижение ставки ×4.47 (`D39.179` п.1) до сих пор читалось как неудобство, и вот порог, за которым оно становится недостижимостью результата. ⚠ **Денег сам стоп НЕ жжёт** — отменённые вызовы этих двух прогонов не успели уйти (латентность 20 и 31 мс, `model_actual` пуст, 0 токенов); неучтённый расход — отдельная строка `PD-441`. ⚠ **Чинится СОГЛАСОВАННО, одной стороной нельзя:** поднять ставку — платформенная половина и она меняет всю шкалу покупки; не бросать летящие вызовы и/или считать резерв по фактической конкуренции — движковая. Проверка без Go: `sqlite3 -header -column "file:?mode=ro" "select trace_id, count(*), round(sum(cost_usd),6) from request_log group by trace_id;"` ⚠ **РАДИУС ПЕРЕ-СНЯТ 04.09 ЗАМЕРОМ ЗА $0 (заказ оркестратора №22): дефект НЕ про хвост длинной книги, а про ПОРОГ, ниже которого книга не переводится ВООБЩЕ.** Порог считается из измеренного, а не из оценки: движок в собственном отказе назвал резерв ОДНОГО редакторского вызова до шестого знака — `committed=$0.278319 reserved=$0.207805, denied estimate=$0.069828, ch5/chunk0/edit` при потолке `$0.548319`. Отсюда волна из четырёх стоит **$0.277633 — наблюдённая величина: три реально стоявших резерва ($0.207805) плюс отказанный ($0.069828)**. ⚠ Не `4 × $0.069828 = $0.279312`: первая редакция ряда взяла именно это произведение, а оно СКОНСТРУИРОВАНО — вызовы волны не равны между собой, промпты чанков разной длины, и `3 × $0.069828 = $0.209484` расходится с наблюдёнными `$0.207805` на `$0.001679` (средний реальный резерв $0.069268 против отказанного $0.069828). Произведение оставлено рядом как ВЕРХНЯЯ оценка, и держать оба стоит: порог считается по обоим одинаково — `$0.277633 / $0.03 = 9.25` и `$0.279312 / $0.03 = 9.31`, — то есть **вывод не зависит от выбора числа**. ⚠ Счёт «три» выведен, а не напечатан: он следует из `waves.workers: 4` минус отказанный, и подтверждается отношением `$0.207805 / $0.069828 = 2.976`. Порог — `chaptersLeft × $0.03 ≥ стоимости волны`, то есть **книга проходит только с 10 непереведённых глав и больше**. Поправка числа — оркестратора №22, 04.09. Следствия: **последние ДЕВЯТЬ глав любой книги недостижимы**, и **книга из ≤9 глав не переводится никогда** — ни первой покупкой, ни повторными, потому что потолок КУМУЛЯТИВНЫЙ (`bookCap = committed + increment`, `internal/runs/spawn.go:212-214`) и запас каждого прогона равен ровно инкременту, сколько бы книга ни потратила раньше. ⚠ **Предъявлено живьём и бесплатно:** пятиглавая книга `bk_P5UCXKHDO4HFWSH3` заведена через настоящий интейк ($0 — `manifest` без ключей), и `GET /v0/books/{id}/run-options` вернул `max_chapters: 5`, то есть **максимум, который пользователь вообще может купить этой книге, — $0.15 против $0.279312 за волну**; для книги пака после первой дочитанной главы тот же вызов вернул `max_chapters: 9` ($0.27). Разрезка короткой книги ПОБАЙТНО та же (юниты по главам 2·1·1·2·1 против тех же 2·1·1·2·1 у первых пяти глав длинной), то есть короткая книга содержит ровно тот кусок `ch5/chunk0`, чей резерв измерен. ⚠ **ЧЕГО В ЗАМЕРЕ НЕТ, называю прямо:** сама короткая книга НЕ запускалась — её прогон способен потратить до $0.15 на черновую волну, а санкция владельца на платные прогоны закрылась вместе с паком; арифметика замкнута, живой прогон короткой книги остаётся неснятым. | open | платный сквозной прогон через API, пак «закрыть цикл» 04.09 (воспроизведено дважды) | | PD-441 | bug | **major, деньги** | движковая половина — `backend/internal/pipeline/stagerun.go`, ветвь с комментарием `No 2xx ever arrived: nothing was billed` (`releaseReservation` + строка `request_log` с нулевой ценой); платформенная — `internal/runs/reconcile.go` `settleOne`, который берёт цифру движка как истину | **ЛЕДЖЕР ДЕНЕГ — НИЖНЯЯ ГРАНИЦА, и потолок сам её создаёт: halt отменяет вызовы, которые УЖЕ УШЛИ к провайдеру, и их цена не записывается никуда.** Замерено 04.09 на `run_FS3O4UO5EDTKVF42`: волна пустила четыре редакторских вызова, один дошёл (6846+13729 токенов, **$0.063404**, 156444 мс) и своим коммитом сорвал книжный потолок, а три оставшихся были отменены В ТОТ ЖЕ МИГ, отработав **120756, 156469 и 156470 мс** — с `cost_usd 0`, нулевыми токенами и пустым `model_actual`. Вызов, проживший 2–2.6 минуты, до провайдера дошёл почти наверняка. **Порядок величин:** соседние ЗАВЕРШЁННЫЕ вызовы того же прогона стоили $0.017409 и $0.063404 ⇒ незаписанное — порядка **$0.05–0.19 на ОДНО срабатывание потолка** против **$0.278319** за всю книгу. То есть механизм, который защищает от перерасхода, сам создаёт неучтённый расход до двух третей стоимости книги за одно срабатывание. ⚠ **Дыра не в принципе, а в ПОКРЫТИИ, и это делает строку заказом, а не наблюдением.** Консервативная оценка в движке уже построена и ратифицирована (`stagerun.go`, «paid 2xx with zero usage; settling the reservation estimate to keep the ceiling honest», pack-13 point-9): проект уже принял правило «нельзя ослеплять потолок нулём там, где вызов был платным». Но условие требует `resp` — ОТВЕТА. Вызов, отменённый в полёте, ответа не получает, уходит другой веткой, и та честно пишет «nothing was billed» — что верно для транспортного отказа, не выпустившего запрос, и НЕВЕРНО для отмены на 156-й секунде. **Направление лечения (именно направление):** сеттлить оценку и при отмене вызова, который успел уйти, различая по ФАКТУ ухода; латентность — кандидат-различитель (20 мс против 156 000 мс разводит два случая без всякого рассуждения), но порог обязан быть обоснован замером, а не назначен. ⚠ Списание провайдером НЕ ДОКАЗАНО: биллинга провайдера у зоны нет, утверждается ровно наблюдаемое — вызовы шли минутами и их цены в леджере нет. ⚠ Правка `stagerun.go` — ЧУЖАЯ зона, отсюда только строка. Смежная строка единого бэклога — **78**. Воспроизведение: `sqlite3 -header -column "file:?mode=ro" "select id, ts, stage, role, latency_ms, cost_usd, err from request_log where err='context canceled' and latency_ms > 1000;"` | open | платный сквозной прогон через API, пак «закрыть цикл» 04.09 (усилено разбором оркестратора №22 по коду движка) | | PD-438 | bug | **major** | `internal/ingest/tail.go` `apply` (греп `ErrForeignStreamAhead`), `internal/ingest/tail.go` `tailFrom` (греп `mine := want`), `internal/runs/reconcile.go` `drainJournal` | **Владение потоком не переживало проход свипа, и чужие события ложились на оплаченную попытку.** `mine` — возвращаемое значение, а не колонка: следующий проход выводит его заново из `pos.LastSeq > 0`. Пока тейлер ПРОХОДИЛ мимо чужого handshake'а, двигая байтовый хинт, следующий проход читал чужую область с курсором, который говорит «это наше»: чужой `ceiling` ставил `paused` живому оплаченному прогону, `unit_done` писал главы, которых никто не покупал, а чужой `seq`, попавший на наш, давал `ErrPayloadConflict` и карантинил здоровую проекцию. ⚠ **Воспроизведено в двух вариантах, до и после правки P13** (копия дерева, `Tail` дважды — форма, в которой его зовёт `drainJournal`): на `HEAD 494c4ef` законный чужой hello травит проекцию (`pass 2 applied a FOREIGN event (seq [2])`), а чужой мажор карантинит; после пере-упорядочивания пункта 3 P13 ОБА травят — то есть заказанная правка расширила старый класс с законных чужих строк на любые. Конфликт payload воспроизводится одинаково на обеих версиях. ⚠ Лечение в дереве пака P13 (03.09): чтение ОСТАНАВЛИВАЕТСЯ на чужом handshake'е, если платформа именовала поток попытки и наши строки уже применены (`pos.LastSeq > 0`), и курсор на нём остаётся — владение выводится заново каждый проход, ценой одного пере-чтения строки. Прежняя семантика «пройти мимо» сохранена там, где она безопасна: пока `LastSeq == 0`, наш handshake ещё может лежать ниже по файлу (пин `TestAForeignStreamBeforeOursIsWalkedPastRatherThanStoppedOn`), и у безымянной легаси-попытки (`want == ""`) — пин `TestAnotherAttemptsStreamInTheSameJournalIsSkipped` зелёный. Пины: `TestAForeignStreamDoesNotOwnOurAttemptOnTheNextPass` (три формы чужого hello × три следующих строки). ⚠ **ДОФИКС 03.09: обоснование «вред ограничен по времени» ОПРОВЕРГНУТО приёмкой исполнением, и оно было моё.** Довод «чужой handshake ⇒ нашего процесса в книге уже нет» неверен: платформа отдаёт КАЖДОМУ спавну одной и той же попытки один и тот же id потока (`internal/runs/spawn.go`, греп `engineStreamID(l.RunID, l.AttemptNo)`), а движок, найдя, что этот id уже писал события книги, минтит СВЕЖИЙ и продолжает (`backend/internal/pipeline/events.go`, греп `fresh := obs.NewTraceID()`). Значит «чужой» hello может писать ЖИВОЙ процесс нашей же попытки, и прогон способен простоять припаркованным весь свой срок. Радиус сужен опровергателем приёмки: путь — конъюнкция двух признанных сбоев (претензия на юнит, отчитавшаяся ошибкой, плюс исчезновение юнита без exit-marker), а не рядовой рестарт. **Наблюдаемость дана в том же дофиксе:** тейлер сообщает о парковке отдельным сигналом (`internal/ingest/tail.go`, греп `ErrForeignStreamAhead`) вместо тихого `nil`; свип пишет WARN с прогоном, попыткой и смещением; и парковка теперь СТОИТ РЯДОМ с карантином в условии канала починки (`maybeResync`, греп `!l.Quarantined && !parked`), то есть у припаркованной попытки снова есть свежесть через `tmctl status`. Пины: `TestTheParkIsReportedSoTheCallerCanTellItFromBeingCaughtUp` (без DSN), `TestAParkedAttemptStillGetsTheRepairChannel` (DSN). ⚠ Чего по-прежнему НЕТ: строки в `runs`, гейджа и колонки — парковка живёт длину одного прохода и в БД не пишется; дать ей строку значит завести колонку, то есть миграцию, а она этим нарядом не заказана ⚠ **ОСТАТОК НАЗВАН 04.09 приёмкой:** лечение в дереве и заленджено (`6ae3e76`), но ПРИПАРКОВАННАЯ попытка не видна ни в `runs`, ни гейджем, ни колонкой — у этого класса нет ни одного из трёх сигналов, которые были у карантина, а `maybeResync` до дофикса такую попытку пропускал. Дофикс дал ей имя (`ErrForeignStreamAhead`), WARN с троттлингом и вход в ресинк; поверхностная видимость для ОПЕРАТОРА остаётся открытой и идёт райдером платформенного пака. ⚠ **ДИСПОЗИЦИЯ ПАКА «ЗАКРЫТЬ ЦИКЛ» (04.09): ОТЛОЖЕНО СОЗНАТЕЛЬНО, и вот довод, а не отсутствие времени.** Все три недостающих сигнала — строка в `runs`, гейдж, колонка — читают ХРАНИЛИЩЕ, а парковка в хранилище не пишется: она живёт длину одного прохода и выводится заново каждым свипом. Дать ей любой из трёх значит завести колонку на `run_attempts`, то есть миграцию и новое состояние, которое надо СНИМАТЬ, — а снимать его некому, кроме того же свипа, который его поставил. Это не райдер, а отдельная работа со своим дизайном («парковка как СОСТОЯНИЕ, а не как исход прохода»), и сделать её заодно с дверью выдачи значило бы решить её мимоходом. Что у класса ЕСТЬ сегодня: имя (`ErrForeignStreamAhead`), WARN с троттлингом и вход в канал починки — оператор, который смотрит в ЖУРНАЛ, парковку видит; невидима она тому, кто смотрит только в таблицу. Строка остаётся открытой ровно на этом. | open | самопроверка пака P13 (веер, линза шва; воспроизведено сессией на копии) |