From cb3c9674bf4ceafbc89e11a3045c9a3253367a4d Mon Sep 17 00:00:00 2001 From: heaven Date: Fri, 11 Sep 2026 00:00:53 +0300 Subject: [PATCH] Correct the log where it blamed a write deadline the API listener does not set, and give four leftovers of an interrupted run their own rows. --- docs/BACKLOG.md | 6 +++++- docs/architecture/05-decisions-log.md | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 42477bc6..9b840be9 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`; обновлять при каждом лендинге):** всего **283** строк · зона бэкенд **138** строго / **185** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 0** (236 закрыта D39.175; **371** — актом D39.233, **360** — актом D39.232, обе 10.09), платные прогоны разблокированы · «скоро» **133** (перечень — грепом по таблице, рукописный список снят D39.126) · гейт-строка эксп-22: 153 (плюс 150 — руки владельца; ⚠ 55 из гейтов СНЯТА — D39.198 п.5); строка 5 — остаток гейчен ре-пробой 74, носитель события теперь строка 188 (аудит D39.140); остальное «когда-нибудь». ⚠ Счёт — НИЖНЯЯ граница долга, не потолок (разбор — легенда таблицы ниже). +> - **СЧЁТ ОЧЕРЕДИ на 10.09 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **287** строк · зона бэкенд **138** строго / **185** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 0** (236 закрыта D39.175; **371** — актом D39.233, **360** — актом D39.232, обе 10.09), платные прогоны разблокированы · «скоро» **136** (перечень — грепом по таблице, рукописный список снят 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; правки — только через оркестратора) @@ -319,3 +319,7 @@ | 395 | **DEV И ПРОД РАСХОДЯТСЯ В ДВАДЦАТЬ РАЗ ПО ВРЕМЕНИ НА СВОРАЧИВАНИЕ, И ЭТО НИГДЕ НЕ ОБЪЯВЛЕНО.** `platform/internal/ingest/supervisor.go`, греп `WaitDelay` — тридцать секунд на dev-пути; `platform/internal/runner/runner.go`, греп `stopGrace` — десять минут на боевом. Один и тот же движок против тех же платных провайдеров получает на свёртку полминуты или десять минут в зависимости от пути запуска. Расхождение само по себе может быть законным, но оно НЕ НАЗВАНО, и dev-замер «успевает свернуться» ничего не говорит о боевом (и наоборот) | платформа | когда-нибудь | оба числа названы в одном месте с доводом, почему они разные, либо сведены | улов платформенной сессии, 10.09 | | 396 | ⛔ **ГЛАВНАЯ РАЗВИЛКА «ПРАВИЛЬНОГО ВОЗОБНОВЛЕНИЯ»: платформа ПЕРЕЗАПУСКАЕТ прогон, вышедший с кодом остановки БЕЗ записанного намерения.** `platform/internal/runs/reconcile.go`, греп `interruptedBySomeoneElse`. Решение осознанное и с записанным доводом (перезагрузка хоста против ручного `systemctl stop`, асимметрия цен), поэтому строка не о дефекте. ⚠ Но инвариант `D39.240` («правильно возобновить») обязан пересмотреть эту развилку ЯВНО, а не унаследовать: сегодня она означает, что прогон, остановленный мимо продуктовой двери, будет поднят заново и продолжит тратить. Названо платформенной сессией как то, что новый пак не должен взять по умолчанию | платформа | скоро | поведение при коде остановки без намерения ПЕРЕ-решено под инвариант корректного возобновления, а не унаследовано | улов платформенной сессии, 10.09 | | 397 | **ПЯТЬ ОТКРЫТЫХ РЯДОВ РЕЕСТРА ПЛАТФОРМЫ ПРО ХОЛДЫ И ОСТАНОВКУ — ОДИН ПРЕДМЕТ, А НЕ ПЯТЬ РАЗРОЗНЕННЫХ.** Их печатает собственный гейт зоны как тревогу. Прямо по предмету остановки и возобновления: **PD-418** (у расчётной строки живого прогона нет ручки, а рантбук обещает оператору обратное) · **PD-244** (единственный выход расчёта, оставляющий холд открытым МОЛЧА) · **PD-162** (книга с удалённым каталогом принимает прогон и заклинивает его навсегда с открытым холдом) · **PD-465** (заявка на спавн коммитится ДО подъёма юнита, третий способ уехать под доказательством не ловится) · **PD-217** (книга с незакрытым холдом блокирует апгрейд движка бессрочно). ⇒ под инвариантом `D39.240` это готовый предметный список «не оставить мусор», уже приоритизированный реестром зоны | платформа | скоро | пять рядов закрыты ОДНИМ предметом либо явно разведены как разные | улов платформенной сессии, 10.09 | +| 398 | ⛔ **ПРОГОН, ВЫШЕДШИЙ БЕЗ МАРКЕРА, ПЕРЕЗАПУСКАЕТСЯ БЕЗ ВСЯКОГО ОГРАНИЧЕНИЯ — капа попыток у платформы НЕТ.** Юнит кончился, маркера нет (сломан `ExecStopPost`, повторная перезагрузка) → после льготного окна спавна и без записанного намерения платформа считает состояние свежим и ПЕРЕЗАПУСКАЕТ: новый холд, новый спавн. На ДОЧИТАННОЙ книге движок выходит сразу и снова без маркера ⇒ цикл повторяется каждым проходом свипа. Замер: `MaxAttempts` в `platform/internal` вне тестов — **7 хитов, все в `jobs.go` и все про очередь**, ни одного про попытки прогона (контроль: слово `restart` в `platform/internal/runs/reconcile.go` есть и читается). ⚠ И тревога МОЛЧИТ: счётчик отсрочек очищается «уликой» каждого рестарта, поэтому порог зависания недостижим. ⚠ **Честная граница (Fable 5, 10.09): путь ЗАМЕРЕН чтением, сам бесконечный цикл ВЫВЕДЕН, прогоном не предъявлен** | платформа | скоро | у перезапуска без маркера есть потолок, а его исчерпание видно оператору | разбор старшего коллеги по инварианту `D39.240`, 10.09 | +| 399 | **ПЛАТЯЩИЙ ПРОГОН МОЛЧА ЗАМИРАЕТ В ИНТЕРФЕЙСЕ: проекция ПАРКУЕТСЯ при ре-спавне той же попытки.** Курсор встречает чужой хендшейк, проекция останавливается до конца попытки — `platform/internal/runs/reconcile.go`, греп `ErrForeignStreamAhead`; комментарий там же признаёт: «the "stranger" may be this very run, alive and writing». ⇒ прогон жив, платит и пишет, а прогресс не двигается. **У состояния «запаркована» нет носителя** — ни столбца, ни счётчика; репэйр-канал чинит оценку времени и свежесть, но не прогресс. Пользователь видит замерший экран без объяснения | платформа | скоро | у запаркованной проекции есть носитель состояния, и пользователю сказано, что прогресс отстал, а не остановился | разбор старшего коллеги, 10.09 | +| 400 | **ПРАВКА БАНКА ЛЕГЛА, ПЛАТФОРМА УМЕРЛА ДО ЗАПИСИ О НЕЙ — СЛЕДУЮЩИЙ ПРОГОН ВОЗЬМЁТ ХОЛД И УМРЁТ НА ГАРДЕ.** `platform/internal/runs/bank.go`, греп `RecordBankMove`: ветка ошибки стора обработана (клиент пере-шлёт, сходится), а смерть ПРОЦЕССА между применением правки и записью факта канала не имеет. Цену называет собственный комментарий кода: отметка о движении банка остаётся пустой ⇒ следующий прогон допускается без пере-снапшота ⇒ умирает на снапшот-гарде уже ПОСЛЕ взятого холда (`PD-425`). ⚠ Прямо на предмете `D39.240`: «не оставить мусор» и «правильно возобновить» — это ровно оно | платформа | скоро | смерть между правкой банка и записью о ней не приводит к взятому холду и умершему прогону | разбор старшего коллеги, 10.09 | +| 401 | **МУСОР НА ДИСКЕ ПОСЛЕ ОБРЫВА: три носителя, ни один не подметается.** **(а)** артефакт сборки при смерти платформы посреди неё остаётся на месте — перечень удаляемого берёт только строки с путём (`platform/internal/exports/exports.go`, греп `Unlinked`); **(б)** временные файлы движка чистит ТОЛЬКО следующая сборка того же идентификатора, которой при обрыве не будет; **(в)** частичный бэкап движка ложится под именем, которого платформа не знает (`platform/internal/runner/backup.go`). ⚠ Отдельно, инертно, но противоречит леджеру: отметка о расчёте может остаться пустой навсегда, если расчёт прошёл, а её запись — нет; читателей столбца вне тестов **0** (греп: только писатели и комментарии) | платформа | когда-нибудь | обрыв не оставляет на диске файлов, которых никто не подметёт, и отметка о расчёте не расходится с леджером | разбор старшего коллеги, 10.09 | diff --git a/docs/architecture/05-decisions-log.md b/docs/architecture/05-decisions-log.md index 8a336392..7d68e6c8 100644 --- a/docs/architecture/05-decisions-log.md +++ b/docs/architecture/05-decisions-log.md @@ -5,6 +5,7 @@ > ⚠ **Эррата 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.235` п.4) — В МОЕЙ НОТЕ ЛОЖЬ ПРО МЕХАНИЗМ УЩЕРБА, и назвал её тот же, кто её принёс.** Пункт 4 писал: блокирующий `Runner.Stop` означает, что «при `WriteTimeout: 30 s` пользователь НЕ получает `202`, который обещает контракт». **Неверно.** Пере-снято мной: тридцать секунд стоят у слушателя МЕТРИК (`platform/cmd/tmplatformd/main.go`, греп `WriteTimeout` — и комментарий там прямо говорит «for once a WriteTimeout too: nothing here streams»), а у API-слушателя `WriteTimeout` НЕТ НАМЕРЕННО (`platform/internal/httpapi/serve.go`, греп `No WriteTimeout`: срезал бы длинный SSE-поток). ⇒ ущерб не «оборванный ответ», а **зависший запрос и съеденный бюджет свипа**; вторую половину — про ложный клин реконсиляции — уже поправила платформенная сессия (`D39.236` п.5), а эта половина простояла в журнале ложью до 10.09. Лечение не меняется: `--no-block`. ⭐ Поправку принёс старший коллега, чья же буква и была неверной, — и принёс её тогда, когда пак уже отменён и исправление ему ничего не давало. Это второй случай за смену, когда замерявший назвал ошибку своего прибора сам. > ⚠⚠ **Эррата 10.09-ж (`D39.238` п.4) — МОЯ АЛЬТЕРНАТИВА БЫЛА НЕГОДНОЙ, и движковая зона объяснила почему; плюс объявлено ИЗВЕСТНОЕ СВОЙСТВО, которое не чинится.** **(а)** Я предложил определять жёсткость не СЧЁТОМ сигналов, а признаком «сигнал пришёл, когда прогон УЖЕ в мягкой остановке». Это не лечит замеренное: слипшийся бит в `sigqueue` неотличим от одного сигнала НИ счётом, НИ состоянием — разделить их может только ВРЕМЯ между ними, и «наблюдаемый вход в фазу остановки» есть ровно оно, снятое прибором. ⇒ контракт из п.3 не просто приемлем, он ЕДИНСТВЕННЫЙ возможный. **(б)** Зона всё равно перевела лестницу со счётчика на СОСТОЯНИЕ ПРОГОНА — по другому и лучшему доводу: авторитет у состояния прогона, а не у переменной в горутине обработчика, и если остановку когда-нибудь попросят не сигналом (ручкой API, вторым каналом платформы), лестница не станет врать. **(в) ⛔ ОБЪЯВЛЕНО ИЗВЕСТНЫМ СВОЙСТВОМ, ЧТОБЫ НИКТО НЕ СЧИТАЛ ЭТО БАГОМ: три нажатия в одном планировочном кванте дают МЕНЬШЕ эскалаций, чем нажатий, и это не чинится.** **(г) И следствие, которое усиливает платформенную половину сильнее, чем я сказал ей раньше:** после ВТОРОГО входа движок возвращает сигналам дефолтную диспозицию (`signal.Reset`), то есть остаётся БЕЗ ОБРАБОТЧИКА — значит повторный `systemctl kill` от свипа для него СМЕРТЕЛЕН, а не идемпотентен. ⇒ долговечная отметка «жёсткий сигнал уже послан» (`D39.238` п.3) — не гигиена, а единственное, что стоит между пере-выпуском свипа и смертью процесса без терминального кадра и без сеттла. > ⚠ **Эррата 10.09-е (`D39.235` п.1) — ДВА ПРАВИЛА ФОРМЫ КАДРА, КОТОРЫХ НОТА НЕ НАЗВАЛА, а без них две зоны решили бы по-разному.** **(а) `Money` на исходе `stopped` — «всегда» надо читать как «всегда, КОГДА СЧЁТЧИКИ ЕСТЬ».** `moneyLedger()` возвращает `nil` не только без потолка, но и когда волн ещё не было (`backend/internal/pipeline/events.go`, греп `moneyLedger`): остановка на ингесте или севе счётчиков не имеет, и это законная пустота, а не умолчание — иначе движок не смог бы исполнить ноту буквой. **(б) ПРАВИЛО ПРИСУТСТВИЯ `Stop` — «есть, если остановку ЗАПРОСИЛИ», а НЕ «если исход `stopped`».** Прецедент дословно у соседнего поля: `Money` present iff a ceiling was REACHED, not iff the outcome is ceiling. Важно ровно в одном случае, и он в паке назван: остановку запросили, а прогон уехал `failed` из-за упавшего соседа — поле обязано БЫТЬ. Оба правила внесены в оба промта до выдачи. ⭐ Нашёл не я: нота прошла ревью старшего коллеги уже после того, как я на неё сослался в паках. > ⚠ **Эррата 10.09-д (`D39.234` п.5) — «13 битых якорей, ВСЕ в чужих зонах» неверно: четыре были в МОЕЙ.** Прибор в тот момент показывал их в `docs/BACKLOG.md` (три указателя в контракт и один в отчёт `research/25`), а я, перечисляя корни целей, прочитал зону ЦЕЛИ вместо зоны ДОКА — тот же класс, что «знаменатель по одному глаголу» (`D39.232` п.5б). Все четыре пере-наведены 10.09. ⇒ норма: у битого якоря ДВЕ зоны, и чинит его владелец ДОКА, а не владелец цели.