From 12c1fe87b669e5aeb38e05da1cc5a3851c89a7e5 Mon Sep 17 00:00:00 2001 From: heaven Date: Fri, 11 Sep 2026 22:31:44 +0300 Subject: [PATCH] Record that the consistency instrument finds no rendering for twenty-six of sixty-nine terms and counts them as consistent, and that escalation ships disabled in every shipping config --- docs/BACKLOG.md | 4 +++- docs/architecture/05-decisions-log.md | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 28514f65..78408bc0 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). Здесь только долг и его диспозиции. -> - **СЧЁТ ОЧЕРЕДИ на 11.09 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **316** строк · зона бэкенд **152** строго / **205** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 0** (236 закрыта D39.175; **371** — актом D39.233, **360** — актом D39.232, обе 10.09), платные прогоны разблокированы · «скоро» **161** (перечень — грепом по таблице, рукописный список снят D39.126) · гейт-строка эксп-22: 153 (плюс 150 — руки владельца; ⚠ 55 из гейтов СНЯТА — D39.198 п.5); строка 5 — остаток гейчен ре-пробой 74, носитель события теперь строка 188 (аудит D39.140); остальное «когда-нибудь». ⚠ Счёт — НИЖНЯЯ граница долга, не потолок (разбор — легенда таблицы ниже). +> - **СЧЁТ ОЧЕРЕДИ на 11.09 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **318** строк · зона бэкенд **154** строго / **207** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 0** (236 закрыта D39.175; **371** — актом D39.233, **360** — актом D39.232, обе 10.09), платные прогоны разблокированы · «скоро» **163** (перечень — грепом по таблице, рукописный список снят 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; правки — только через оркестратора) @@ -352,3 +352,5 @@ | 431 | ⛔ **СЕССИИ ЗОН НЕ МОГУТ СНЯТЬ ПОЛНЫЙ МУТАЦИОННЫЙ КАТАЛОГ ПО СРЕДЕ, А ОРКЕСТРАТОР МОЖЕТ — и это делает «оркестратор добирает за зону» НОРМОЙ ПО УМОЛЧАНИЮ.** Замер 11.09: бэкенд-сессию убило ЧЕТЫРЕ раза подряд на пере-снятии десяти записей (дважды 0 из 10, падало на первой). Причина — не машина и не нагрузка: `dmesg` даёт **26** убийств, **все с `constraint=CONSTRAINT_MEMCG`**, каждое в своём транзиентном юните `tm.slice/tm-runs.slice/tm-test-*`; сами слайсы при этом `memory.max = max`. А прогон оркестратора идёт в `/init.scope` с `memory.max = max` и доходит до конца (проверено: тот же каталог, та же команда, 10 из 10 сняты). ⇒ **разница не в умении, а в СКОУПЕ**, и пока она есть, полный каталог структурно может снять только оркестратор. ⛔ **Цена этого — ползучая подмена рубежа:** приёмка обязана СУДИТЬ чужую работу, а не производить её измерения; если зона по среде не может снять свой главный гейт, то «пере-снял своим прибором» (норма) незаметно превращается в «сделал за неё» (подмена). ⭐ Различение, которым это лечится, ратифицируется актом пака консистентности: **пере-снял = повторил ИМЕЮЩЕЕСЯ измерение зоны; сделал за неё = произвёл измерение, которого у зоны НЕТ, и вписал как её.** ⛔ **И ВТОРАЯ ПОЛОВИНА, названная САМОЙ ЗОНОЙ, без которой ряд лечит не то: дело не только в потолке.** Её слова: даже без потолка она потеряла бы прогон ДВАЖДЫ — первый раз потому, что пустила второй мутатор по чужому корню, второй раз потому, что `diff -rq` убедил её, будто результаты целы. ⇒ **из четырёх потерь две — СРЕДЫ, две — МЕТОДА.** Ряд обязан ловить оба: «зона не может снять свой гейт по среде» И «зона портит собственный гейт методом». Иначе потолок починят и удивятся, что не помогло. ⇒ лечение по существу: поднять потолок транзиентных юнитов до замеренного пика `tmmutate` ЛИБО гонять полный каталог в скоупе без потолка объявленно, с числом из `dmesg` рядом — но не оставлять это молчаливым свойством среды | оркестратор/владелец | **скоро** | потолок юнитов поднят до замеренного пика ЛИБО объявлено, что полный каталог снимает оркестратор, и почему | замер оркестратора 11.09, разбор со старшим коллегой | | 432 | **СТЕНД-СТУПЕНЬ ГЕЙТА НЕ МОЖЕТ БЫТЬ ЗЕЛЁНОЙ НА ЭТОМ ХОСТЕ: одному её измерению нужны данные, которые НАМЕРЕННО не версионируются, и после вывода полигона из скоупа их некому доставить.** Замер 11.09 при приёмке: `TM_MINER_PARITY=1 go test -race ./internal/miner/` даёт FAIL с текстом «required data is missing: `eval/exp16/data/jieba_dict_general_zh.txt`» — и в МОЕЙ копии, и в НАСТОЯЩЕМ дереве. Причина названа самим репозиторием: `.gitignore:39` исключает `eval/exp16/data/` с комментарием «exp16 versioned contrast corpus (jieba 0.42.1 dict.txt, 5MB) — SHA pinned in the report, reproducible». ⇒ **это НЕ дефект пака и НЕ дефект дерева**, а дыра провизионирования: флаг `TM_MINER_PARITY=1` по замыслу превращает скип в падение («missing data FAILS instead of skipping»), и на хосте, куда корпус не доставляли, он падает по назначению. ⚠ **Вторая половина хуже первой: владельцем `eval/` был полигон, а зона выведена из скоупа (`D39.248`)** — значит доставлять корпус теперь некому, и ступень будет падать у каждого, кто исполнит рецепт. ⭐ Различать надо ДВА измерения стенда: **корпус разметки** (`books/gu-zhenren/labels`, 6 записей) — на месте, ступень идёт, и именно она защищает прибор консистентности; **контраст майнера** — отсутствует. ⇒ закрытие ряда: либо рецепт доставки корпуса с пинованным SHA в носителе стенда, либо честное объявление «измерение контраста майнера на этом развёртывании не бежит», но не молчаливое красное | бэкенд/оркестратор | **скоро** | рецепт доставки корпуса ЛИБО объявленное отсутствие измерения | приёмка пака консистентности, 11.09 | | 433 | ⛔ **ЛЕКАРСТВО ОТ ДВОЙНОЙ ОПЛАТЫ ПОСТРОЕНО И СТОИТ ВЫКЛЮЧЕННЫМ — и помогает ОДНОМУ случаю из четырёх.** Пак 11.09 закрыл НАБЛЮДАЕМОСТЬ (видно, сколько и на что) и построил ЛЕЧЕНИЕ (ступень усилия ниже при том же бюджете на классе `FlagEmpty`), но ручка `retries.lower_effort_on_empty` лендится с дефолтом `false`: **деньги продолжают течь**, и ничто в дереве этого не остановит, пока ключ не поставят в `true`. ⚠ Дефолт выбран не из робости: включение двигает форму попытки ≥1, а бамп версии политики потолка стоил бы пере-перевода **989 единиц на 24 книгах, $9.99 уже оплаченного** (дедуплицированный замер зоны). ⛔ **И охват уже — из четырёх двойных оплат прогона A лекарство достаёт ОДНУ:** `edit` ($0.071009 = 67 % потери) стоит на `off`, у которого шаг вниз есть; три черновых ($0.035463 = 33 %) уже на `low` — **дне эмитирующей шкалы, и лекарства для них в паке НЕТ**. ⇒ условие включения одно и измеримое: **эхо-контроль ИМЕННО В РЕДАКТОРСКОЙ РОЛИ на плотном CJK, парным дизайном на повторах.** ⚠ Прежняя оценка риска цитировала НЕ ТУ РОЛЬ: «1 чистый китайский выход из 16» — это `flash` как ПЕРЕВОДЧИК, а единственная стадия, которую ручка двигает, — РЕДАКТОР, и квирки прямо говорят, что его эхо-безопасность при `low` на CJK **не мерена**. Ошибку нашёл направленный читатель зоны, не я и не она. ⚠ Вторичный исход того же замера, стоящий $0 сверх прогона: **растёт ли размышление вместе с потолком на `low`** — ровно тот вопрос, который `D39.90` п.9 объявил непроверенным, и он решает, рычаг ли флор | бэкенд/владелец | **скоро** | эхо-контроль в РЕДАКТОРСКОЙ роли на CJK ⇒ ручка в `true`; плюс лечение для дна шкалы | пак «за одну единицу платим дважды», 11.09 | +| 434 | ⛔ **ПРИБОР КОНСИСТЕНТНОСТИ НЕ НАХОДИТ ПЕРЕДАЧУ У 26 ТЕРМОВ ИЗ 69 И МОЛЧА СЧИТАЕТ ИХ КОНСИСТЕНТНЫМИ.** Замер оркестратора 11.09 на артефактах прогона A (`~/tm-coldrun-a/evidence/spread.json`, 69 записей; контроль печатает сам прибор: «terms rendered at all: 40, control: terms considered 69»): у **29** термов `renderings={}`, и для **26 из 29** ФИНАЛЬНАЯ форма банка РЕАЛЬНО стоит в отгруженном тексте — `三转` «третий оборот» · `三转蛊师` · `丫头片子` · `五更天` · `仙师` · `侧室` · `十大奇蛊` · `古月一族` · `唐诗宋词` · `四更时` · `宗族祠堂` · `宗祖祠堂` · `开窍` · `怀璧之罪` · `方之一脉` · `方家` · `更夫` · `梆子` · `正派` · `白家寨` · `老嬷嬷` · `话事人` · `邪魔` · `雪盐` · `魔道` · `魔道巨擘`. ⇒ **`spread=0` у них значит «передачи НЕ НАШЁЛ», а читается как «форма одна»**, и итоговая строка «TERMS SHIPPED IN MORE THAN ONE SHAPE: 0» посчитана по 40 термам, а не по 69. ⚠ Матчер ловит СЛОВО, а не форму: у `高脚吊楼` он вернул «Свайные», тогда как текст несёт «свайного дома / свайные дома / свайных домов». ⛔ Это приоритет №1 владельца и ЕДИНСТВЕННЫЙ прибор, который его меряет; сегодня он отвечает за 58 % книги, а отчитывается за 100 %. Носитель разбора — эррата 11.09-з | бэкенд | **скоро** | знаменатель печатается ВСЕГДА («нашёл у M из N»), ненайденные проверяются текстом, а не засчитываются в консистентные; матчер сверяется формой, а не словом | приёмка прогона A, пере-снято 11.09 | +| 435 | **ЭСКАЛАЦИЯ НЕ МОЖЕТ ИСПОЛНИТЬСЯ НИ НА ОДНОМ БОЕВОМ КОНФИГЕ — построена, ратифицирована и выключена данными.** `backend/configs/pipeline-c1.yaml:223`=`budget_usd: 0`, и комментарий строкой выше говорит прямо: «budget_usd=0 ⇒ эскалация НЕ исполняется (НИ chains, НИ stage.escalate_to)… **Приёмочная сессия 蛊真人 ОБЯЗАНА выставить budget_usd>0**, иначе echo-эскалация черновика (D18) не выстрелит. Дефолт 0 держит CI/Ф0 зелёными без gemini/mistral-ключей». ⇒ дефолт выбран под ЗЕЛЕНЬ CI, а не под продукт, и требует ручного оверрайда, которого не сделал ни прогон A (его §10 п.2 честно пишет «эскалация не исполнялась ни разу»), ни прогон B (решение оркестратора 11.09: НЕ трогать, чтобы сохранить байт-в-байт тождество покупающего файла A↔B — сравнимость денег дороже одного непроверенного пути). ⛔ Значит путь эскалации не исполнялся живьём ДВА прогона подряд, и его первое живое исполнение стоит отдельного дешёвого замера, а не контаминации сквозного прогона. ⚠ Класс тот же, что у ряда 433: **механизм лендится выключенным, и выключенность живёт в ДАННЫХ, где её не видит ни один гейт** | бэкенд | скоро | адресный $0.03-замер пути эскалации отдельным прогоном ЛИБО решение владельца о боевом дефолте | прогон B, пре-рег 11.09 | diff --git a/docs/architecture/05-decisions-log.md b/docs/architecture/05-decisions-log.md index ace318a2..545d1f3c 100644 --- a/docs/architecture/05-decisions-log.md +++ b/docs/architecture/05-decisions-log.md @@ -89,6 +89,26 @@ > ⚠ **Эррата 11.09-д (`D39.248` п.4) — «ЧЕТЫРЕ СТРОКИ СВЁРНУТЫ В ОДНУ» НЕВЕРНО ДВАЖДЫ, и вторая половина была не опиской, а НЕДОДЕЛКОЙ.** Пере-снято мною самоаудитом через час после лендинга ноты: полигонных строк в таблице активных промтов было **ТРИ** (`ХОЛОДНЫЙ ПРОГОН A` · `РЕМОНТ ПРИБОРА` · `фаза Д`), а не четыре — четвёртый файл (`PHASE_D_HANDOFF`) строкой таблицы никогда не был. ⛔ **И свернулись только ДВЕ:** моя правка шла от строки `РЕМОНТ ПРИБОРА` до `Фронт`, а `ХОЛОДНЫЙ ПРОГОН A` стоит в таблице ВЫШЕ и уцелел — то есть в таблице час провисели ДВЕ строки про один и тот же закрытый пак, сводная и прежняя. Строка удалена, в таблице осталась одна полигонная. ⭐ Класс — мой же, записанный в этой смене: **«моя правка на месте» не значит «сделано то, что я объявил»**; объём правки надо сверять с объявленным, а не с намерением. Поймал самоаудит ноты, не читатель. > ⚠ **Эррата 11.09-е (`D39.247`, шапка «Что принято») — «33 файла… НЕ КОММИЧЕНЫ» УСТАРЕЛО В ТОТ ЖЕ ЧАС.** Пере-снято 11.09: улики прогона закоммичены в отдельный репозиторий книг коммитом `ef0509f` («четыре артефакта нельзя воспроизвести ни за какие деньги»), **33 файла, 4953 вставки**, все тридцать три под контролем версий (`git -C books ls-files 'gu-zhenren/door-to-file' | wc -l` = 33). ⛔ **Но они НЕ ОТПРАВЛЕНЫ: репозиторий книг на один коммит впереди `origin/main`.** Владелец санкционировал пуш словом «Да» 11.09, мой классификатор прав его не пропустил, и выполнить его должен владелец у себя: `git -C books push origin HEAD`. ⇒ единственная копия улик платного прогона живёт на ОДНОЙ машине. ⚠ Класс: утверждение о состоянии дерева, верное в момент написания, протухает молча — у него нет ни гейта, ни даты. +> ⚠ **Эррата 11.09-з (`D39.247` п.4, числа консистентности прогона A) — ПРИБОР СВЕРЯЛСЯ НЕ С ТОЙ ВЕРСИЕЙ +> БАНКА И ВИДЕЛ МЕНЬШЕ ПОЛОВИНЫ КНИГИ; обе половины найдены 11.09, первую нашла сессия прогона B, вторую — +> я, пере-проверяя первую.** **(1) Граница.** Прибор сверял отгруженный текст со СТОП-снимком банка +> (`evidence/bankstop-paid/project.db.bank.json`), а читатель получил текст, сделанный под ФИНАЛЬНЫМ банком: +> второй заход терминологии ИЗМЕНИЛ передачу у **12 термов из 69** (пере-снято мною независимо, список сошёлся +> дословно: `凤雏` «Молодой Феникс»→«Юный Феникс» · `老嬷嬷` «старая мамка»→«старая нянька» · `高脚吊楼` +> «дом на сваях»→«свайный дом» · `舅母` «жена дяди по матери»→«тётя по матери» · `白家寨` «селение Бай»→«крепость +> рода Бай» и ещё семь). Порядок снят по `request_log`: терминология-2 кончилась 01:42:10, редакторская волна шла +> 01:43:54→01:54:44 ⇒ редактор целиком работал ПОСЛЕ коммита финального банка. В тексте стоят ФИНАЛЬНЫЕ формы. +> ⇒ вердикты «отдано НЕ банковской формой» по этим термам — артефакт сверки со старой версией, а не промах +> движка. Класс — `D39.228` п.5: фикстура обязана назвать границу, которую прошла; не покраснело нигде, потому +> что числа вышли правдоподобные. **(2) Знаменатель, и он хуже.** Прибор нашёл передачу у **40 термов из 69**, +> у **29** вернул `renderings={}` — и молча записал их в `spread=0`, то есть в консистентные. Я взял эти 29 и +> проверил текстом: финальная форма банка РЕАЛЬНО стоит в отгруженном файле у **26 из 29** (поимённо — ряд 434). +> ⇒ итоговая строка прибора «TERMS SHIPPED IN MORE THAN ONE SHAPE: 0» посчитана по 40 термам, а не по 69, и +> «расхождения нет» там неотличимо от «передачи не нашёл». Контроль прибор печатает честно («terms rendered at +> all: 40, control: 69») — **ловушка в том, что заголовок читается как утверждение о КНИГЕ.** ⚠ И моя ложная +> тревога по дороге, названная здесь, чтобы её не повторили: я объявил находкой «один терм тремя формами», а это +> оказались ДВА РАЗНЫХ исходных терма (`宗族祠堂` и `宗祖祠堂`), которые банк различает верно. +> ⚠ **Тело ноты не переписано** (`D23.3`): п.4 читать вместе с этой эрратой. > ⚠ **Эррата 11.09-ж (`D39.246` п.5, число «0 FAIL» в рецепте стенда) — ЧИСЛО БЫЛО УСЛОВНЫМ, И УСЛОВИЕ Я НЕ НАЗВАЛ.** Моя приёмка вписала в `platform/docs/STACK_DECISIONS.md` «20 ok · 0 FAIL · 5 скипов» как свойство рецепта. Замер платформенной сессии 11.09 (контроль: 15 файлов возвращены к `git show HEAD:`, два новых удалены, тот же FAIL воспроизведён) показывает: **рецепт не может дать зелёный `internal/runner` НИ ПРИ КАКОЙ конфигурации** — $0-шаблон нужен `TestTheSnapshotGuardIsLoudWithoutTheFlagsAndPassesWithThem`, и он же обнуляет проекцию цены, которую читают `TestTheRealEnginesPriceProjectionIsReadByThisBuild` и `TestWhatTheLiveManifestCarriesAndThisBuildDeclinesToRead`. Моё «0 FAIL» согласуется с ПЛАТНЫМ шаблоном плюс выполненным условием скипа. ⛔ **И под этим лежит опасность дороже неверного числа: предикат скипа неверен ПО РОДУ.** `platform/internal/runner/translate_resnapshot_live_test.go:41-43` слушает `127.0.0.1:11434` и скипает, только если порт ЗАНЯТ, — то есть отвечает на вопрос «свободен ли порт», а НЕ «разрешил ли кто-нибудь тратить деньги». Условий скипа три (переменные гейта · порт · артефакт контраста), и **хорошо провизионированный хост выполняет все три** — то есть платная ветвь рецепта сработает именно там, где всё настроено правильно. Прецедент верного рода в дереве есть: `TM_LIVE`. ⇒ раздел «Гейты батареи» остановлен ⛔-баннером зоной 11.09 до починки; носитель — `П-22`. ⭐ **Класс, и он мой: я вписал в чужой носитель число, не назвав условий, при которых оно снято.** Условное число без условия читается как свойство предмета — и следующая смена получит красное и решит, что сломала сама. Нашла зона, не я. > ⚠ **Эррата 11.09-б (АДРЕС ИСПР. 11.09: носители — промт пака и таблица `docs/README.md`, НЕ тело `D39.247`) — «18 РАСХОЖДЕНИЙ» ЗАВЫШЕНО В 2.5 РАЗА, настоящих СЕМЬ.** Замерено бэкенд-сессией пака консистентности 11.09 по-юнитно: из восемнадцати промахов **11 ложные** — банковская форма доехала до читателя в том же юните СКЛОНЁННОЙ, а `SameStem` её не признал, потому что стеммер снимает ровно одно окончание и стеммы выходят разной длины (во всех одиннадцати один — строгий префикс другого с разницей в ОДИН знак). Пять из одиннадцати — намеренно исключённый мягкий знак (`D39.71`), и записка в дереве сама называет лечение — «anchor-gated match». ⇒ **акт остаётся верен в направлении и неверен в величине:** механизм консистентности работает и работает не везде, но НЕ-мест семь, а не восемнадцать. Носитель — строка бэклога **419**. ⚠ Класс: я опубликовал число, снятое ОДНИМ прибором, не спросив, что этот прибор не видит. ⛔ **Второй класс, пойманный ревью 11.09: эррату я адресовал НЕ ТУДА.** Числа «18» в теле `D39.247` нет ни разу — пере-снято грепом по живым докам (контроль: прибор прочёл 101 файл `docs/`, число нашлось в промте пака, в строке таблицы `docs/README.md`, в журнале и в ряду **419**). Акт говорит ДРУГУЮ величину на ДРУГОЙ популяции — «16 из 69, из них 6 отданы не банковской формой», снятую глазами по отгруженному файлу, и поправка стеммера её НЕ трогает. ⇒ поправлять надо было промт и таблицу, а акт — не надо было. ⭐ Урок: **у эрраты есть адрес, и он проверяется грепом ЧИСЛА по телу ноты, а не памятью о том, откуда я его взял.** > ⚠ **Эррата 11.09-в (D39.247 п.4 и вопрос владельцу) — Я ПИСАЛ ПО ОСИ, КОТОРОЙ В ПРОДУКТЕ НЕТ.** Акт называл «подписанный/неподписанный ТЕРМИН» и ставил владельцу вопрос «закон ли для неподписанного». Владелец 11.09: «нет такого понятия, подписан либо ВЕСЬ банк, либо он в неконсистентном состоянии; есть галочка скипать подпись — тогда автоподписывание». Это ратифицировано `D39.144` (подписывается банк ЦЕЛИКОМ; пер-термная подпись — НЕ модель продукта; дефолт — авто-продолжение с неподписанным банком, строки едут с пометкой). ⇒ **вопрос был сформулирован неверно и снят.** ⭐ Но под ним лежит РЕАЛЬНАЯ находка, и она дороже: в коде есть пер-термный автомат `auto|draft|approved`, и пост-проверка считает нарушением только `approved` — **механизм решает по единице, которой в продуктовой модели не существует.** Носители — строки **407** (исправлена) и **419**. Мерить надо состоянием КНИГИ (подписана целиком либо авто-продолжение), а не статусом отдельного терма.