diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index cb745234..41a7574e 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -293,13 +293,13 @@ | 370 | **Anthropic-адаптер не несёт капа ретраев, который есть у общего транспорта.** `backend/internal/llm/provider_anthropic.go` возвращает `BilledDecodeError` с `retryable=true` без ограничителя `billedDecodeSeen`, который стоит в `httpllm.go` — то есть на этом адаптере оплаченный 2xx может повториться до `max_attempts`, а не один раз. ⚠ **Боевой цены сегодня НОЛЬ:** поверхность deprecated, Anthropic убран из стека 04.07, вызовов по этому пути в прогонах нет. ⇒ сознательно вынесено ИЗ пака «вызов, который оборвали мы» (строка 360) по доводу ревью: пак получил бы тесты и мутации на мёртвую поверхность. Чинить при воскрешении адаптера — тогда же и проверять, применим ли к нему признак доставки | бэкенд | когда-нибудь | адаптер получает тот же кап ЛИБО удалён вместе с поверхностью | ревью пака «вызов, который оборвали мы», 08.09 | | 372 | **СТРАЖ ОТМЕНЫ ГЛОТАЕТ НАСТОЯЩУЮ ДЕНЕЖНУЮ ОСТАНОВКУ, когда прогон отменил не человек, а упавший сосед.** Платформа получит `failed` вместо `paused`, а книга без денег упрётся в потолок на ПЕРВОЙ резервации следующего прогона. Сессия стража оставила и была права: ратифицированный комментарий ветки `waitAborted` требует ровно этого для ОБОИХ случаев. ⇒ предмет не движковый, а **продуктовое решение о шве**: должна ли платформа отличать «остановлено человеком» от «остановлено падением соседа» ⛔ **ЗАМЕР 10.09 (движковая сессия, ЧТЕНИЕМ; пере-снят оркестратором): в дереве ЖИВЁТ УТВЕРЖДЕНИЕ, которое дерево же опровергает.** Источников отменённого контекста **ДВА**, и по `ctx.Err()` они неотличимы: человеческий сигнал (`backend/cmd/tmctl/main.go`, греп `signal.NotifyContext`) и **собственная отмена волны** — `runWave` заводит производный контекст (`backend/internal/pipeline/waverun.go`, греп `context.WithCancel(parent)`) и отменяет его из ветвей инфра-ошибки соседа и паники. При этом ДВА не-тестовых комментария прямо декларируют, что источник один: `backend/internal/pipeline/bankdecisions.go`, греп `by nothing else`, и `backend/cmd/tmctl/main.go`, греп `a cancelled run is one somebody asked to stop`. ⇒ ни `exitCode`, ни `terminal()` двух причин не различают, а комментарии учат следующего, что различать нечего. ⚠ Полный инвентарь носителей отмены (4 источника, около двадцати читателей, с адресами по HEAD) — в секции движковой зоны `docs/PROGRESS.md`; он вход любого пака про корректность остановки | бэкенд | скоро | шов различает две отмены ЛИБО объявлено, что не различает, с ценой | сдача пака «вызов, который оборвали мы» 08.09 | | 373 | **НА HTTP/2 НАШ ДЕДЛАЙН ИНЕРТЕН, если тело запроса больше окна пира, а провайдер отвечает ранним 2xx, не дочитав его.** Замер движковой сессии: вызов длится столько, сколько держит сервер — **4 с и 8 с при дедлайне 700 мс**, — и всё уезжает в `decode_error`, ту самую дверь, которую пак 360 объявил закрытой. Механизм: запись паркуется в `awaitFlowControl` на `sync.Cond`, которую контекст НЕ будит. ⚠ Дыра ПРЕДШЕСТВУЕТ паку и требует пирa, отвечающего не дочитав тело (НЕ форма DeepSeek). Лечится `http2.Transport.WriteByteTimeout`, но его значение — НОВЫЙ порог, а пороги назначать нельзя | бэкенд | когда-нибудь | дедлайн действует и на записи тела ЛИБО класс объявлен недостижимым с замером | сдача пака «вызов, который оборвали мы» 08.09 | -| 375 | **ВЫГРУЗКА ЧИТАЕТ ОТМЕНЁННУЮ ПОЗИЦИЮ КАК ВЫПАВШИЙ КУСОК ТЕКСТА.** `memberDrops` считает строку `cancelled` выпавшим членом редакторской единицы — то есть выносит КОНТЕНТ-вердикт по факту нажатия кнопки: читателю сообщается, что из единицы пропал текст, тогда как пропала лишь незаконченная работа. Семантика экспорта, не транспорт. ⚠ Исчезает само вместе с классом `cancelled`, если денежная половина строки **360** будет урезана. Найдено критиком полноты седьмого круга; диспозиция оркестратора жила письмом и носителя не имела до `D39.231` ⚠ **ДОБАВКА 10.09 (замер движковой зоны по запросу оркестратора, подтверждён ключом БД).** Правка «применить `resolvedForResume` внутри `memberDrops`» ПОЛНА для этого пути (живой вызов один — `backend/internal/pipeline/export.go:307`=`drops := memberDrops(u, byChunk, draftStageNames)`) и попутно чинит артефакт порядка: внутренний цикл `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`) ⛔ **ВТОРАЯ ПОЛОВИНА, вытащенная 10.09 из отменённого пака, чтобы не умереть вместе с ним (ПОДОЗРЕНИЕ — выведено ЧТЕНИЕМ кода движковой сессией, фикстурой НЕ гонялось):** строка `chunk_status` не добавляется, а ЗАМЕЩАЕТСЯ — апсерт по ключу стадии с `DO UPDATE SET … final_hash = excluded.final_hash` (`backend/internal/store/chunkstatus.go`, греп `on conflict`), а запись отмены кладёт ПУСТОЙ `final_hash`. ⇒ прогон, который ПЕРЕ-атакует позицию с уже готовой строкой (редрайв · пере-пин с `--resnapshot` · правка исходника) и будет оборван, оставляет позицию с пустым хешем — выгрузка теряет текст, чекпойнт которого лежит на диске. То есть отмена способна стереть НАСТОЯЩИЙ вердикт, а не только пометить незаконченное. Проверяется фикстурой «готовая строка → редрайв → обрыв» ⚠ **ИСПР. 11.09 — ОДИН ИЗ ТРЁХ МАРШРУТОВ ОПРОВЕРГНУТ ЧТЕНИЕМ, и опровергла его АВТОР прежней формулировки:** через РЕДРАЙВ сценарий НЕДОСТИЖИМ. `ResetChunkStages` (`backend/internal/store/chunkstatus.go`, греп `func (s *Store) ResetChunkStages`) в ОДНОЙ транзакции удаляет и `checkpoints`, и `chunk_status` сбрасываемых стадий — пере-снято мной: две `DELETE` между `BeginTx` и `Commit`; то же говорит доккоммент `backend/internal/pipeline/volume.go` (греп `DELETES the unit`). ⇒ к моменту пере-атаки замещать нечего, прежнего вердикта уже нет. **Остаются ДВА маршрута**, оба обходят быстрый путь резюма при ЖИВОЙ прежней строке: пере-пин `--resnapshot` (снапшот уехал, признак пере-пинуемости ложен) и правка исходника или промта (не сходится хеш содержимого). Их и предъявляет фикстура; исход «недостижимо» — такой же результат ⚠ **РАСШИРЕН 16.09 актом `D39.255`: то же и для МЕТКИ СТОПА.** `memberDrops` считает выпавшим члена с ЛЮБОЙ флагованной черновой строкой и причину не спрашивает (пере-снято приёмкой на обоих деревьях: `cancelled` → 1, `retry_unaffordable` → 1, контроль `hard_refusal` → 1; предикат байт-идентичен HEAD). ⇒ читатель получает «ТРЕБУЕТ ПРОВЕРКИ ЧЕЛОВЕКОМ», а оператор — «missing for good … cannot bring those back» там, где следующий прогон с поднятым потолком доделает и члена, и эдит. Верная починка — третье состояние члена («не доделан, но доделается»); фильтр в `memberDrops` меняет ложь на молчание.| бэкенд | скоро | единица, неполная ИЗ-ЗА ОСТАНОВКИ, отличима и от выпавшего контента, и от полной: ни ложного контент-вердикта, ни молчания | седьмой круг ревью пака 360, 08.09; добавка — замер зоны 10.09 | +| 375 | **ВЫГРУЗКА ЧИТАЕТ ОТМЕНЁННУЮ ПОЗИЦИЮ КАК ВЫПАВШИЙ КУСОК ТЕКСТА.** `memberDrops` считает строку `cancelled` выпавшим членом редакторской единицы — то есть выносит КОНТЕНТ-вердикт по факту нажатия кнопки: читателю сообщается, что из единицы пропал текст, тогда как пропала лишь незаконченная работа. Семантика экспорта, не транспорт. ⚠ Исчезает само вместе с классом `cancelled`, если денежная половина строки **360** будет урезана. Найдено критиком полноты седьмого круга; диспозиция оркестратора жила письмом и носителя не имела до `D39.231` ⚠ **ДОБАВКА 10.09 (замер движковой зоны по запросу оркестратора, подтверждён ключом БД).** Правка «применить `resolvedForResume` внутри `memberDrops`» ПОЛНА для этого пути (живой вызов один — `backend/internal/pipeline/export.go:307`=`drops := memberDrops(u, byChunk, draftStageNames)`) и попутно чинит артефакт порядка: внутренний цикл `status.go:538-543` оставляет ПОСЛЕДНЮЮ флагнутую черновую строку, а строки приходят `ORDER BY … stage`, поэтому при двух черновых стадиях `cancelled` может перебить контентную причину; фильтр возвращает контентную. Случай «сверху лёг `cancelled`» НЕВОЗМОЖЕН: строка апсертится по `(book_id, chapter, chunk_idx, stage)` (`backend/internal/store/chunkstatus.go:99`=`ON CONFLICT (book_id, chapter, chunk_idx, stage)` — ⚠ испр. 16.09: было :92), а контентно флагнутая стадия не пере-выполняется (резюм-гейт `backend/internal/pipeline/stagerun.go:93`=`resolvedForResume(cs)`). ⛔ **НО правка меняет ЛОЖЬ на МОЛЧАНИЕ, и это не закрытие:** если единственный флаг члена — `cancelled`, после фильтра `DroppedMembers` = 0 и `DroppedReason` пуст (`export.go:320-322`), единица уходит `ok`, молча не досчитавшись текста члена. ⇒ закрывать надо ТРЕТЬИМ состоянием («единица неполна из-за остановки»), а не одним фильтром. Окно узкое: нужна комбинация «финальная строка от прежнего прогона + редрайв члена + стоп» (`export.go:174`) ⛔ **ВТОРАЯ ПОЛОВИНА, вытащенная 10.09 из отменённого пака, чтобы не умереть вместе с ним (ПОДОЗРЕНИЕ — выведено ЧТЕНИЕМ кода движковой сессией, фикстурой НЕ гонялось):** строка `chunk_status` не добавляется, а ЗАМЕЩАЕТСЯ — апсерт по ключу стадии с `DO UPDATE SET … final_hash = excluded.final_hash` (`backend/internal/store/chunkstatus.go`, греп `on conflict`), а запись отмены кладёт ПУСТОЙ `final_hash`. ⇒ прогон, который ПЕРЕ-атакует позицию с уже готовой строкой (редрайв · пере-пин с `--resnapshot` · правка исходника) и будет оборван, оставляет позицию с пустым хешем — выгрузка теряет текст, чекпойнт которого лежит на диске. То есть отмена способна стереть НАСТОЯЩИЙ вердикт, а не только пометить незаконченное. Проверяется фикстурой «готовая строка → редрайв → обрыв» ⚠ **ИСПР. 11.09 — ОДИН ИЗ ТРЁХ МАРШРУТОВ ОПРОВЕРГНУТ ЧТЕНИЕМ, и опровергла его АВТОР прежней формулировки:** через РЕДРАЙВ сценарий НЕДОСТИЖИМ. `ResetChunkStages` (`backend/internal/store/chunkstatus.go`, греп `func (s *Store) ResetChunkStages`) в ОДНОЙ транзакции удаляет и `checkpoints`, и `chunk_status` сбрасываемых стадий — пере-снято мной: две `DELETE` между `BeginTx` и `Commit`; то же говорит доккоммент `backend/internal/pipeline/volume.go` (греп `DELETES the unit`). ⇒ к моменту пере-атаки замещать нечего, прежнего вердикта уже нет. **Остаются ДВА маршрута**, оба обходят быстрый путь резюма при ЖИВОЙ прежней строке: пере-пин `--resnapshot` (снапшот уехал, признак пере-пинуемости ложен) и правка исходника или промта (не сходится хеш содержимого). Их и предъявляет фикстура; исход «недостижимо» — такой же результат ⚠ **РАСШИРЕН 16.09 актом `D39.255`: то же и для МЕТКИ СТОПА.** `memberDrops` считает выпавшим члена с ЛЮБОЙ флагованной черновой строкой и причину не спрашивает (пере-снято приёмкой на обоих деревьях: `cancelled` → 1, `retry_unaffordable` → 1, контроль `hard_refusal` → 1; предикат байт-идентичен HEAD). ⇒ читатель получает «ТРЕБУЕТ ПРОВЕРКИ ЧЕЛОВЕКОМ», а оператор — «missing for good … cannot bring those back» там, где следующий прогон с поднятым потолком доделает и члена, и эдит. Верная починка — третье состояние члена («не доделан, но доделается»); фильтр в `memberDrops` меняет ложь на молчание.| бэкенд | скоро | единица, неполная ИЗ-ЗА ОСТАНОВКИ, отличима и от выпавшего контента, и от полной: ни ложного контент-вердикта, ни молчания | седьмой круг ревью пака 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) + пере-замер оркестратора | | 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 | ⛔ **СНЯТА 10.09 ВМЕСТЕ С ОТМЕНОЙ ПАКА «ДВЕ ОСТАНОВКИ» (`D39.240`) — предмет строки был «у человека одна кнопка и она жёсткая», а владелец решил, что жёсткой и достаточно.** Тело не воспроизводится: замеры, ради которых строка стояла, ратифицированы нотами и живут там — блокирующий `systemctl stop` без `--no-block` и инертность повторного стопа на `deactivating` (`D39.237`), коалесценция двух сигналов в один (`D39.238`), диагноз «у вопроса о трате нет единственного хозяина» (строка **385**). ⚠ **Единственная часть прежнего тела, которая пережила отмену и живёт своей жизнью, — строка 391**: из зависшего сворачивания нет выхода, кроме убийства, и оно не оставляет терминального кадра. Строка сохранена надгробием, а не удалена, потому что на неё ссылаются `D39.233`–`D39.240` | бэкенд | когда-нибудь | ссылки на 381 ведут к нотам, а не в пустоту | `D39.240` | -| 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 | +| 382 | ⛔ **ПОМЕТКА «ЭТО ОЦЕНКА» — УСЛОВИЕ СЛОВА ВЛАДЕЛЬЦА (`D39.230` п.1) — ПО ШВУ НЕ ЕДЕТ И ПОТРЕБИТЕЛЯ НЕ ИМЕЕТ.** Владелец разрешил списывать с читателя по ОЦЕНКЕ при условии, что пометка стоит. В движке она есть: `backend/internal/pipeline/status.go:321`=`UnsignedBankTerms int` — `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 | | 383 | **ПОТОЛОК ОЖИДАНИЯ ЖИВЁТ КОНСТАНТОЙ В ОДНОЙ ЗОНЕ, А ВЫВОДИТСЯ ИЗ КОНФИГА ДРУГОЙ — ФОРМА, КОТОРАЯ ТУХНЕТ МОЛЧА.** Платформенный грейс остановки обязан покрывать законное ожидание движка. Сегодня это число платформа ВЫВОДИТ из движкового каталога и кладёт себе константой (пак «две остановки», `D39.236` п.6): любая правка `backend/configs/models.yaml` — новый провайдер, поднятый `edit_ceiling_out`, изменённый `tok_s_floor` — делает константу ниже правды, и никто об этом не узнает: превышение грейса даёт SIGKILL, а SIGKILL не оставляет ни пометки `cancelled`, ни сеттла оценки. ⛔ **И ловушка уже названа числом, а не гипотезой (замер платформенной сессии 10.09):** `attempt_max_s` инертен у ПЯТИ провайдеров из восьми, и у беcключевых `DeadlineFor` сверху не ограничена вовсе — она растёт с бюджетом линейно. Беcключевой `zai` перерастает сегодняшний максимум 1240 с (deepseek) при бюджете ≥ **43 400** токенов, то есть при `edit_ceiling_out` ≈ **9 900** против сегодняшних **8 000**. ⇒ запас по конфигу — меньше четверти. **Нетухнущая форма, которую надо построить:** движок ПЕЧАТАЕТ свой потолок ожидания сам (он и так умеет считать его `DeadlineFor` — функция экспортирована ровно для вопроса о конфигурации), платформа читает его НА БУТЕ и отказывается стартовать с грейсом ниже. ⚠ В паке «две остановки» этого НЕТ ни в одной половине — там принята сегодняшняя форма (пин на арифметику + строка реестра + комментарий с адресом), и она честно названа тухнущей | бэкенд | скоро | грейс платформы не может молча оказаться ниже законного ожидания движка: число выводится ИЗ ДВИЖКА в рантайме, а расхождение ловится на буте, а не SIGKILL-ом | эхо-протокол платформенной сессии по паку «две остановки», 10.09 (`D39.237` п.5) | | 384 | **ЛЕНДИНГА НЕТ — А ПРОДУКТ ПО СЛОВУ ВЛАДЕЛЬЦА СОСТОИТ ИЗ ДВУХ ПОВЕРХНОСТЕЙ.** 10.09 владелец назвал модель: «фронт — это скорее IDE для переводов; к нему нужна обёртка в виде ЛЕНДИНГА; модель такая же, как у любого ИИ-продукта — лендинг с продуктом, и IDE отдельно, с аккаунтами, оплатой и всем таким» (`D39.239` п.1). Сегодня в репозитории есть только вторая поверхность, и та наполовину: `frontend/` — две маршрутные точки и витрина компонентов (About · AddBook · Bank), зона ЗАМОРОЖЕНА словом владельца с 15.08 и разморозка — отдельным его словом. Лендинга нет ни кодом, ни требованием в `docs/product-requirements.md`, ни строкой до этой. ⚠ **Практическое следствие, из-за которого строка заведена СЕЙЧАС, а не когда дойдут руки:** когда владелец скажет размораживать фронт, надо будет знать, ЧТО размораживают — витрина относится к IDE, и склейка её с лендингом в один поток была бы ошибкой архитектуры, а не темпа | фронт | когда-нибудь | у продукта названы обе поверхности: что делает лендинг, что IDE, где проходит граница входа и оплаты — и это записано требованием, а не устной моделью | слово владельца 10.09, `D39.239` п.1 | | 385 | ⛔ **У ДВИЖКА НЕТ ЕДИНОГО МЕСТА, ОТВЕЧАЮЩЕГО НА ВОПРОС «МОЖНО ЛИ ЕЩЁ ПОКУПАТЬ» — их ЧЕТЫРЕ, и каждая новая причина остановки протягивается руками через все.** Замер 10.09 (вопрос владельца «почему остановка задела 18 файлов — нет ли архитектурного косяка»): причин перестать тратить деньги сегодня ТРИ, и каждая живёт своим набором носителей — денежный потолок `CeilingHalt` в **6** файлах (`internal/pipeline/{events,escalation,stagerun,waverun,reservegate}.go` + `cmd/tmctl/main.go`), объёмный грант `VolumeStop` в **4** (`internal/pipeline/{volume,bookrun,waverun}.go` + `cmd/tmctl/main.go`), остановка человеком — в **7**. **Пересечение несущее:** `waverun.go` и `cmd/tmctl/main.go` несут ВСЕ ТРИ, `stagerun.go` и `events.go` — две из трёх. Причина в том, что решение «тратить или нет» принимается в ЧЕТЫРЁХ независимых точках, каждая в своём словаре: фидер волны («выдать индекс?»), денежный гейт («впустить резервацию?»), петля ретраев («начинать попытку N+1?»), попартийный приём банка («брать ещё партию?»). ⚠ **Это НЕДО-абстракция, а не оверхед:** лишнего не построено, не хватает ОДНОЙ сущности — распорядителя допуска к трате, — и за её отсутствие платит каждая новая причина, протягиваясь четырежды. Денежный потолок заплатил ту же цену раньше, чем остановка вообще появилась. ⛔ **Чинить СЕЙЧАС не надо и это часть диспозиции:** введение единого распорядителя трогает ровно те пути, где живут деньги, ради выгоды, которая проявится только на ЧЕТВЁРТОЙ причине; риск больше пользы. Делать, когда эта область откроется по другому поводу или когда причина появится четвёртая | бэкенд | когда-нибудь | у вопроса «можно ли ещё покупать» ОДИН распорядитель, а причины (потолок · грант · остановка · будущие) — его входы, а не независимые протяжки; новая причина стоит одной реализации, а не четырёх | замер оркестратора 10.09 по вопросу владельца об инвазивности пака «две остановки» | @@ -324,7 +324,7 @@ | 404 | ⚠ **СТРОКА ПЕРЕ-ПИСАНА 11.09: её центральное утверждение было МОЕЙ ОШИБКОЙ.** Первая редакция говорила, что мутация с уехавшим якорем «не краснеет, перестаёт сажаться, и в отчёте „не посадилась“ неотличимо от „пережила“», и что механизма против этого НЕТ. **Механизм есть, и он в самом `tmmutate`:** запись с уехавшим якорем получает вердикт `rotted`, печатается отдельной строкой и **засчитывается как неожиданный исход — прогон завершается ненулевым кодом**; довод записан в самом инструменте (`backend/cmd/tmmutate/main.go`, греп `an entry whose anchor moved guards nothing`). Пере-снято мной по возражению движковой сессии, которая в этот класс наступила и была поймана СВОИМ ЖЕ прибором: `make mutations` дал `MAKE-EXIT=2 · RED 151 · SURVIVED 0 · ROTTED 1`, и протухшей оказалась ЧУЖАЯ запись, сломанная её правкой. ⛔ **ЖИВОЙ ОСТАТОК КЛАССА — не мутации, а ДОКИ И КАТАЛОГИ, где такого счётчика нет.** Линтер доков сверяет ПО СОДЕРЖИМОМУ только якоря опт-ин токен-формы; голый `файл:строка` он не видит по построению, и его протухание не даёт ни красноты, ни строки в выводе (замер 10.09: из 19 голых якорей живых секций реестра долга съехали 10, при 9 из 9 целых токен-формных). ⭐ И побочный урок, названный сессией: знаменатель проверки якорей надо брать ИЗ КАТАЛОГА, а не из своих правок — она проверила три свои записи и не проверила чужую, которую сломала | доки | скоро | у голых `файл:строка` в живых доках есть счётчик, дающий ненулевой выход, — либо они переведены в токен-форму | находка движковой сессии 11.09, пере-снята оркестратором | | 405 | **РАЗМЕН ПО СТРОКЕ 375 ИМЕЕТ НАЗВАННУЮ ЦЕНУ: проекция расходится с леджером на оборванной попытке.** Починка «обрыв не стирает уже заработанный вердикт» (заландена `3084766`) оставляет позиции прежний `cost_usd`, поэтому деньги ОБОРВАННОЙ попытки в попозиционную проекцию не добавляются: **леджер полон, проекция нет**. Названо самой движковой сессией при сдаче, а не найдено потом. ⇒ тот, кто будет сверять проекцию с леджером на книге, где были обрывы поверх отгруженных позиций, увидит расхождение и не должен принять его за потерю денег — деньги в леджере. ⚠ Заведено ОТДЕЛЬНОЙ строкой намеренно: это цена размена, который оркестратор утвердил осознанно (потеря отгруженного текста хуже неточности отчёта), и она обязана стоять рядом с ним, а не всплыть через месяц | бэкенд | когда-нибудь | попозиционная проекция и леджер сходятся на книге с обрывами поверх отгруженных позиций, либо расхождение объявлено в самой проекции | сдача движковой сессии 11.09 | | 406 | ⛔ **КОНСИСТЕНТНОСТЬ КНИГИ НИКТО НЕ СПРАШИВАЕТ — ПРИБОРА ПО ОТГРУЖЕННОМУ ТЕКСТУ НЕТ ВОВСЕ.** Мера «сколько РАЗНЫХ передач дал один термин» ПОСТРОЕНА (`Spread`/`Variants`, `backend/internal/pipeline/mining.go:384-384`=`how many DISTINCT renderings the drafts produced`), но считается по ЧЕРНОВИКУ, на стопе майнинга, ДО редакторской волны, и читает её только человеческая таблица подписи. По ФИНАЛЬНОМУ тексту такой меры не существует ни одной. ⇒ приоритет №1 владельца («консистентные термины на всю книгу») сегодня НЕПРОВЕРЯЕМ в принципе: все четыре A/B, показавшие пользу банка, меряли ПРИСУТСТВИЕ инъектированной формы в чанке, а не отсутствие конкурирующей. **Лечение $0 и переиспользует готовое:** тот же счёт по отгружаемому тексту + строка отчёта сборки «термин X отдан тремя формами в главах 12, 340, 901». Предмет не пуст: полигонный прибор на стенде дал 13–16 % терминов с более чем одной передачей. ⚠ Это ЕДИНСТВЕННАЯ работа, которая делает качество ПРЕДЪЯВИМЫМ; без неё кривую деградации по главам снять не с чего ⛔ **ИСПОЛНЕНО ЧАСТЬЮ 11.09 ПРОГОНОМ A, и первый же результат снял мою же формулировку слепого пятна.** Прибор построен полигоном и дал по отгруженному тексту «0 терминов из 40 отданы более чем одной формой». ⚠ **Ноль держался на том, что прибор НЕ СМОТРЕЛ:** кандидаты искались регуляркой, требующей ЗАГЛАВНОЙ буквы (`[А-ЯЁ][а-яёА-ЯЁ-]+`), и склонённые строчные формы («гу-мастера», «наложницей») отпадали ДО правила сравнения. Я заказал переписать правило сравнения пословно — **заказ бил мимо**: критик применил этот рецепт к прибору на боевых данных, и не вернулся НИ ОДИН из 29 потерянных термов. Починка ГЕНЕРАЦИИ кандидатов на тех же данных даёт **48 найденных термов вместо 40** — это замер, и он стоит. ⛔ **А «шесть строк со `spread ≥ 2`» ИСПР. 11.09 — я процитировал число КРИТИКА, которое полигон ОТОЗВАЛ в том самом отчёте, что я принял актом.** Её собственный пере-замер той же починки даёт **девять**, и все девять — ШУМ: оконный поиск глотает соседние слова и считает каждое окно отдельной формой («старейшины и», «Фан не», «глава рода он»). Её формулировка дословно: «покрытие 40 → 48 — замер; счёт расхождений у починенного прибора — шум», улика — `checks/spread-candidate-fix-probe.txt`, и окну недостаёт фильтра, отвергающего лишние слова. ⇒ **числа расхождений у починенного прибора НЕТ вовсе**, и заказ следующему — сначала фильтр окна, потом счёт. ⭐ Класс моей ошибки: я взял число из отчёта, не дочитав до раздела, где его автор от него отказался. ⚠ И заголовочный «0» неустойчив к порогу складывания: под пословным правилом `沈翠` распадается на «Шэнь»/«Шэнь Цуй» и число становится 1. ⇒ остаток ряда: прибор по ФИНАЛЬНОМУ тексту с (а) генерацией кандидатов без регистрового фильтра, (б) названным порогом складывания, (в) замеренной чувствительностью к нему. ⭐ **ПРАВИЛО ВЫБОРА ПЕРВИЧНОЙ КОЛОНКИ — ратифицируется актом приёмки пака 11.09; записано здесь, чтобы пережило смену, и УЖЕ ИСПРАВЛЕНО ДВАЖДЫ.** У прибора две колонки: СТРОГАЯ (равенство стеммов, как у пост-проверки) и ЯКОРНАЯ (признаёт склонённую передачу). **Первичная — ЯКОРНАЯ, строгая идёт полом точности рядом.** ⛔ **Направления ошибок, иначе довод читается наоборот:** строгая ошибается в сторону ЛОЖНОЙ ТРЕВОГИ (форма доехала склонённой, а она объявляет нарушение); якорная — в сторону СОКРЫТИЯ (засчитывает совпадение, которого по смыслу нет). **Как детекторы нарушения на прогоне A:** строгая дала 18 сообщений, настоящих 7 ⇒ точность **0.39**; якорная дала **9** при 7 настоящих ⇒ **0.78**. ⚠ **Число ИСПР. 11.09 и было 1.00 ДО ужесточения `NearStems` до строгого префикса** — ужесточение вернуло `邪魔` («злой»/«злог» — замена, не усечение) в ложные, и это цена размена, которым закрыты три коллизии многословных передач. Вторая ложная — `话事人`, однословная передача, якорить нечем. ⇒ **каждое пятое сообщение первичной колонки ложное, и читать её надо с этой поправкой.** ⇒ основание НЕ «ложная тревога дешевле сокрытия» (так было написано, и так оно аргументирует ОБРАТНОЕ), а такое: **детектор, врущий в 61 % сообщений, не громче — его перестают читать, а непрочитанный прибор не защищает ничего.** ⛔ **Второе основание, от данных не зависящее:** писатели `decl.forms` в не-тестовом Go только сидовые (`internal/seed/normalize.go:85`=`d := Decl{Invariant: t.Decl.Invariant`, `internal/membank/memseed.go:166`=`b, mErr := json.Marshal(declInfo{`), майнер форм не пишет, а дверь `BankCorrection` контракта поля `decl`/`forms` не несёт вовсе ⇒ на дорожке продукта форм нет НИ У КОГО, и строгая колонка там вырождается в одношаговое равенство. ⚠ Иллюстрация «0 форм из 69 в банках прогона» основанием НЕ является. ⛔ **ДВА ЧИСЛА ТОЧНОСТИ, путать нельзя (замерено зоной 11.09): само ОТНОШЕНИЕ строгого префикса — 17 из 30 равномерной выборки принятых пар, ≈0.57** (`рода~родной`, `гор~горная`, `так~также` — разные леммы); **колонка в своих ограждениях — 11 из 11 на прогоне A.** ⇒ работает не отношение, а ограждения (якорь · многословное окно · границы предложения), и их эффективность замерена на ОДНОЙ книге. Названный отказ: где слово банковской передачи окажется соседом другого слова в той же позиции окна, колонка соврёт в сторону сокрытия. ⛔⛔ **ПРАВИЛО ДЕЙСТВУЕТ ПРИ УСЛОВИИ: пока прибор ФЛАГУЕТ и не отказывает.** Станет он гейтом отгрузки — ложная тревога начнёт стоить удержанного текста или пере-генерации, то есть ДЕНЕГ, асимметрия перевернётся, и порядок колонок надо решать заново. ⇒ **триггеров пере-открытия ДВА:** (1) появился МАШИННЫЙ писатель `decl.forms`; (2) **эта колонка стала гейтом** — носители: `Gates.Glossary.PostcheckGate` построен (`backend/internal/pipeline/quality.go:361`=`gateOn := r.Pipeline.Gates.Glossary.PostcheckGate`, свёрнут в снапшот) и выставлен в НУЛЕ из четырёх живых конфигов, плюс ряд **296** (точечная перегенерация по промаху — направление владельца, работы нет). ⚠ Второй триггер сегодня БЛИЖЕ первого. ⚠ Прежнее моё решение (первичная — строгая) принято и отменено 11.09 в один день; цена разворота — согласие с ручным оракулом 13/16 против 10/16, **и это n=16: направление, а не величина.** Так же и 0.57 — порядок, а не третья цифра.| бэкенд | **скоро** | счёт расхождения форм по отгруженному тексту + строка отчёта сборки | аудит банка 11.09, `research/34` §2.1 | -| 407 | **ПОСТ-ПРОВЕРКА БАНКА СПРАШИВАЕТ НЕ ТО: присутствие формы ГДЕ-НИБУДЬ в чанке вместо формы НА МЕСТЕ срабатывания, и про конкурирующую передачу не спрашивает никогда.** `backend/internal/membank/mempostcheck.go` — вопрос bag-of-words по всему чанку ⇒ дрейф ВНУТРИ составного имени невидим по построению. Замерено исполнением на копии: ряд `方源`→«Фан Юань» с алиасом `古月方源`, выход «Гу Юэ Фан Юань» даёт 0 промахов при подписанном каноне `古月 → Гуюэ`; контроль рядом (форма реально отсутствует) даёт 1 промах, то есть прибор спрошен и отвечает. ⚠ **У экземпляра ДВА корня, и смена вопроса закрывает только один:** ряд `古月` съеден равнотрастовым длиннейшим матчем (`memory.go` `suppressContained`) МОЛЧА. Дешёвая половина — проверять алиас-ключ на СОБСТВЕННУЮ канонную форму (день); дорогая (детектор значения по исходнику) — ряд **286**, загейчен замером. ⚠ Класс объявлен в дереве с 06.09 (`experiments/23-editor-tier.md` §Д50.3) — новизна нулевая, ряд заводится как НОСИТЕЛЬ, которого у него не было ⛔ **ИСПР. 11.09 ПО СЛОВУ ВЛАДЕЛЬЦА — РЯД БЫЛ НАПИСАН ПО НЕСУЩЕСТВУЮЩЕЙ ОСИ.** Слово: «нет такого понятия [подписанный термин], подписан либо ВЕСЬ банк, либо он в неконсистентном состоянии; есть вариант поставить галочку скипать подпись с фронта — тогда автоподписывание». Это ратифицировано `D39.144`: подписывается банк ЦЕЛИКОМ, пер-термная подпись прямо названа НЕ моделью продукта, дефолт движка — авто-продолжение с неподписанным банком, строки едут С ПОМЕТКОЙ. ⇒ **настоящий дефект глубже, чем «прибор не видит дрейфа»: в коде живёт пер-термный автомат `auto|draft|approved` (`backend/internal/store/migrate.go:192`), и пост-проверка считает нарушением ТОЛЬКО `approved` — то есть принимает решение по ЕДИНИЦЕ, КОТОРОЙ В ПРОДУКТОВОЙ МОДЕЛИ НЕТ.** Это и есть причина нуля при 18 найденных (строка **419**): сломана не поверхность, а ОСЬ решения. Закон `D39.104` на проводе один для всех строк; состояние книги — «подписана целиком» либо «авто-продолжение», и мерить надо им. | бэкенд | скоро | вопрос пост-проверки + видимость подавления вложенного ряда | аудит банка 11.09, находка `K3` (единственная пережившая верификацию) | +| 407 | **ПОСТ-ПРОВЕРКА БАНКА СПРАШИВАЕТ НЕ ТО: присутствие формы ГДЕ-НИБУДЬ в чанке вместо формы НА МЕСТЕ срабатывания, и про конкурирующую передачу не спрашивает никогда.** `backend/internal/membank/mempostcheck.go` — вопрос bag-of-words по всему чанку ⇒ дрейф ВНУТРИ составного имени невидим по построению. Замерено исполнением на копии: ряд `方源`→«Фан Юань» с алиасом `古月方源`, выход «Гу Юэ Фан Юань» даёт 0 промахов при подписанном каноне `古月 → Гуюэ`; контроль рядом (форма реально отсутствует) даёт 1 промах, то есть прибор спрошен и отвечает. ⚠ **У экземпляра ДВА корня, и смена вопроса закрывает только один:** ряд `古月` съеден равнотрастовым длиннейшим матчем (`memory.go` `suppressContained`) МОЛЧА. Дешёвая половина — проверять алиас-ключ на СОБСТВЕННУЮ канонную форму (день); дорогая (детектор значения по исходнику) — ряд **286**, загейчен замером. ⚠ Класс объявлен в дереве с 06.09 (`experiments/23-editor-tier.md` §Д50.3) — новизна нулевая, ряд заводится как НОСИТЕЛЬ, которого у него не было ⛔ **ИСПР. 11.09 ПО СЛОВУ ВЛАДЕЛЬЦА — РЯД БЫЛ НАПИСАН ПО НЕСУЩЕСТВУЮЩЕЙ ОСИ.** Слово: «нет такого понятия [подписанный термин], подписан либо ВЕСЬ банк, либо он в неконсистентном состоянии; есть вариант поставить галочку скипать подпись с фронта — тогда автоподписывание». Это ратифицировано `D39.144`: подписывается банк ЦЕЛИКОМ, пер-термная подпись прямо названа НЕ моделью продукта, дефолт движка — авто-продолжение с неподписанным банком, строки едут С ПОМЕТКОЙ. ⇒ **настоящий дефект глубже, чем «прибор не видит дрейфа»: в коде живёт пер-термный автомат `auto|draft|approved` (`backend/internal/store/migrate.go:192`=`term status machine`), и пост-проверка считает нарушением ТОЛЬКО `approved` — то есть принимает решение по ЕДИНИЦЕ, КОТОРОЙ В ПРОДУКТОВОЙ МОДЕЛИ НЕТ.** Это и есть причина нуля при 18 найденных (строка **419**): сломана не поверхность, а ОСЬ решения. Закон `D39.104` на проводе один для всех строк; состояние книги — «подписана целиком» либо «авто-продолжение», и мерить надо им. | бэкенд | скоро | вопрос пост-проверки + видимость подавления вложенного ряда | аудит банка 11.09, находка `K3` (единственная пережившая верификацию) | | 408 | **ДАННЫЕ БАНКА НЕ ДОСЕЯНЫ, И ИМЕННО ИЗ-ЗА ЭТОГО ГЕЙТ КОНСИСТЕНТНОСТИ НЕЛЬЗЯ ВКЛЮЧИТЬ.** ⛔ **ЧИСЛО ИСПР. 11.09 — ПРЕЖНЕЕ БЫЛО ДО-СТЕММЕРНОЙ БАЗОЙ, а ряд гейтил им решение владельца.** Пере-снято мною прогоном пинов размеченного корпуса (`TM_CHECKER_LABELS=1 go test ./internal/membank/ -run TestK6 -v`): **база (`metrics.json`, до стеммера и до #10) — tp=1 fp=14 ⇒ 0.0667**, и это то, что здесь стояло; **ОТГРУЗОЧНЫЙ путь СЕГОДНЯ — tp=1 fp=6 tn=251 ⇒ 0.1429**; со стеммизацией `decl`-форм — **fp=0 ⇒ 1.0000**. ⚠ И концентрация названа неверно: класса `转` на отгрузочном пути НЕТ ВОВСЕ (его снял стеммер), остаточные шесть — это `元石` ×2 и `蛊虫` ×4, то есть **ровно склонительный разрыв, лечащийся досевом форм**. ⇒ направление ряда верно, величина отставала на два улучшения, а список ложных описывал уже не тот прибор. ⭐ **И это прямо смыкает ряд с 423:** стеммизация `decl`-форм уводит точность в 1.0000, а `decl`-форм нет ни в одной машинной выгрузке — то есть «досеять данные» и есть лечение, но на дорожке продукта досевать некому. Три дыры, все в ДАННЫХ, не в Go: (а) глагольные и многословные термины засеяны с пустыми формами — один `炼化` дал 7 ложных промахов (⚠ **испр. 11.09: это число из ДРУГОГО замера** — `docs/experiments/23-editor-tier.md:7630`=`Все семь «промахов»`, не из `labels/metrics.json`; в размеченном корпусе `炼化` нет вовсе, и обе величины верны порознь, но ссылка была одна на две); (б) односимвольный ханьский ключ структурно вне автомата без `allow_short` (порог 2 знака, `membank/memory.go:72`=`minKeyLenHan = 2`), а это самый частый класс канона в китайском ранобэ (蛊, 道, 气); (в) сид-линт об этом молчит. ⚠ Владелец 05.09 отложил гейт до замера ложных срабатываний («если он ложно положительно будет стрелять — это плохо, очень плохо») — вот их источник, и он лечится досевом, а не кодом ⚠ **11.09: это один из ТРЁХ инстансов общего класса — ряд 423** (майнер эмитит только своё поле; на немайненном корпусе меры вырождаются). | бэкенд+полигон | **скоро** | досев форм · `allow_short` у односимвольных · линт сида, который об этом кричит | аудит банка 11.09, `research/34` §2.3 | | 409 | **РЕДАКТОР — ИСТОЧНИК РАЗНОБОЯ, А НЕ СТРАХОВКА ПОВЕРХ БАНКА, и это замерено дважды.** Без закон-блока редактор ломает 5–12 из 20 уже ВЕРНЫХ клеток черновика (`experiments/18-editor-wire-probe.md` §A.1); с подключённым банком боевой редактор держит ХУДШЕЕ покрытие канона в панели — 0.882 против 0.909 у черновика, который он правит (`experiments/22` §6.2, 1124 упоминания). Два механических носителя: черновая волна выбирает по БАЗОВОМУ банку с исключением намайненного (`bankmaterialize.go:329`) ⇒ на первом прогоне книги черновик идёт вообще без добытого банка; редакторская волна, единственная с полным банком, теряет липкость МЕЖДУ правочными единицами внутри главы (`waverun.go:717`) при **86 % многоединичных глав (1962 из 2283)**. ⇒ закон-блок обязан стать инвариантом конфига, а не следствием выбора модели редактора. ⚠ Инцидентов дрейфа от потери липкости — НОЛЬ наблюдений, замерена только экспозиция: правка без замера была бы стрельбой вслепую. ⛔ **ИСПР. 11.09: «замер снимается прогоном A» назначал работу ЗАКРЫТОМУ прогону.** Прогон A принят актом `D39.247`, зона полигона выведена из скоупа целиком (`D39.248` п.4) — ждать этого замера больше не от кого. **Что прогон A реально дал по предмету — ЭКСПОЗИЦИЯ на живой книге и совпадение при N=1:** манифест дал `docs/experiments/24-door-to-file.md:807`=`units_total 2 / chunks 2` у главы 1 при `units_total 1 / chunks 2` у глав 2 и 3, то есть **одна многоединичная глава из трёх**, и единственный термин книги, отданный ДВУМЯ формами (`开窍大典`), стоит обеими формами в ТОЙ ЖЕ главе 1 (`docs/experiments/24-door-to-file.md:329`=`ДВЕ формы, и обе в одной главе`). ⚠ **Это наблюдение, а не доказательство:** атрибуция в отчёте доведена до ГЛАВЫ, а не до правочной ЕДИНИЦЫ — переход разнобоя ЧЕРЕЗ границу единицы не показан, и N=1. ⇒ по-юнитный замер дрейфа несёт БЭКЕНД на боевой книге, а решение по липкости принимается ПО ЕГО ИТОГУ: закрытие прогона A убрало исполнителя, а не требование | бэкенд | скоро | закон-блок инвариантом конфига + по-юнитный замер дрейфа на боевой книге и решение по липкости между правочными единицами по его итогу (прогона A ждать нечего — `D39.247`) | аудит банка 11.09, `research/34` §2.4 | | 410 | ⚠ **ПЕРЕПИСАН 11.09 — ПРЕЖНЯЯ ГОЛОВА БЫЛА НЕВЕРНА. Голос НЕ «мёртв в четырёх местах»: он ПОСТРОЕН СКВОЗЬ И ВЫКЛЮЧЕН ДВУМЯ ТУМБЛЕРАМИ.** Пере-снято мною прибором 11.09. **Построено:** схема сида несёт голос (`internal/seed/seed.go:25`=`Voices []Voice`, `Voices []Voice \`yaml:"voices"\``) · загрузчик секции есть (`membank/memvoice.go:79`=`func loadVoiceSections` `loadVoiceSections`, профиль собирается на `:121`) · хранилище есть (`store.VoiceProfilesForBook`, порядок пинован `bankmaterialize.go:214`) · инъекция в банк построена и УСЛОВНА (`membank/memory.go:511`=`if !in.InjectVoice` по `BankInput.InjectVoice`) · флаггер судит ОТГРУЖЕННЫЙ текст (`pipeline/voicerun.go:42`=`runVoiceChecks runs the voice flagger`) и печатает число в отчёт качества (`backend/internal/pipeline/quality.go:159`=`VoiceFlags int` `voice_flags`) · у гейта есть ручка и потребитель в Go (`quality.go:492`, `voicerun.go:46` по `Gates.Voice.Enabled`). **Выключено ровно двумя вещами.** ⛔ **(1) ДАННЫХ НЕТ НИ ОДНОЙ СТРОКИ, и знаменатель прежней записи был не тот.** Пере-снято: секция `voices:` встречается в **22** файлах дерева, и все 22 ПУСТЫ (не «21 из 22») — но все 22 суть МАШИННЫЕ выгрузки (`*.mined-signature.yaml`, `*.auto-bank.yaml`), то есть их пустота лишь пересказывает «майнер профили не эмитит» и самостоятельной уликой не является. Настоящий знаменатель — **22 рукописных сида с `terms:`, и ключа `voices:` нет НИ В ОДНОМ** (контроль: прибор прочёл 47 файлов с `terms:`, из них 25 машинных отброшены по имени). ⇒ за всю историю проекта профиль голоса не написал никто. **(2) `InjectVoice` — константа `false` без ключа конфига**, и комментарий `pipeline/bankmaterialize.go:295`=`InjectVoice is FALSE and has no config knob` называет и причину (`D21` п.2 держит инъекцию до полигонного замера), и ЦЕНУ включения: «Wiring the injection means setting it and accepting a full --resnapshot». Третьим, помельче: `gates.voice` не выставлен ни в одном из четырёх живых конфигов (контроль: соседние ключи `banknote`, `terminology`, `coverage`, `sanitizer`, `regression_guard` выставлены — прибор спрошен у существующего предмета). ⭐ **ВОПРОС ВЛАДЕЛЬЦУ СНЯТ ЕГО ЖЕ СЛОВОМ 11.09: «Входит»** — голос входит в приоритет №1 наравне с терминами; **проектирует и контролирует голос старший коллега (Fable 5), исполняет опус**, и пак обязан велеть сессии обсудить имплементацию с ним. Второй вопрос связки («если не подпись профилей, то кто») тоже снят: `D39.144` — подписывается банк ЦЕЛИКОМ, пер-профильной подписи в модели продукта нет. ⇒ **строка из вопроса стала ЗАДАЧЕЙ, и она мельче, чем читалась: не «построить голос», а написать профиль, открыть ручку и заплатить ре-снапшотом.** ⚠ Числа у механизма по-прежнему нет ни одного — первый замер обязан идти ПЕРЕД включением | бэкенд/владелец | **скоро** | профиль в сид · ключ конфига вместо константы · замер флаггером по отгруженному тексту; форму проектирует Fable 5 | аудит банка 11.09 (`research/34` §2.5, §4 п.2); голова ИСПР. 11.09 пере-замером оркестратора, слово владельца 11.09 | @@ -334,7 +334,7 @@ | 414 | **ПРИЗНАК ОТКАЗА НЕ ЖИВЁТ В `err`: прогон, где четверть денег купила пустоту, читается через эту колонку как идеально чистый.** Замер 11.09 на живом прогоне: `err` пуст на ВСЕХ 33 строках `request_log`, включая четыре провальные (контроль: строк с `err <> ''` — 0). ⚠ **ИСПР. 11.09 — «единственный носитель» НЕВЕРНО, их ТРИ, и они сходятся точно.** Пере-снято мною по базе прогона (`bk_ROEHZBD46ALFI43E/project.db`, 33 строки): `ok = 0` ровно на четырёх провальных и `1` на остальных 29 · `finish_reason = 'length'` на тех же четырёх · `degraded` (`empty` ×3, `length` ×1). Каждый из трёх, просуммированный по деньгам, даёт **ровно $0.106472** — то есть провал виден тремя независимыми колонками, и `ok` из них самая очевидная. Верна ровно половина прежнего утверждения: пуст именно `err`. ⇒ любой аудит денег или отказов, написанный через `err` — а это первое, к чему потянется рука, — не увидит 25.4 % траты. Родня ряда **376**: там оплаченный обрыв не виден в оценочной паре, здесь провал не виден в колонке ошибок | бэкенд | скоро | отказ обязан быть видим тем же прибором, которым смотрят деньги | критик чисел прогона A, 11.09 | | 415 | ⛔ **ЧЕТВЕРТЬ ДЕНЕГ КНИГИ КУПИЛА ПУСТОТУ, И ПОДНЯТЫЙ ФЛОР ЭТОГО НЕ СНЯЛ — доля БОЛЬШЕ, чем у инцидента, ради которого флор поднимали.** Замер 11.09: 4 вызова из 27 вернулись без пригодного текста и стоили **$0.106472 = 25.4 %** книги; три из них вернули РОВНО 0 знаков ($0.094835 = 22.6 %), четвёртый — выброшенный обрезок. Самый дорогой одиночный вызов прогона ($0.071009, `edit`/`deepseek-v4-pro`) не дал НИЧЕГО. ⚠ Мина задокументирована (размышление съедает `max_tokens`), боевой `pipeline-c1.yaml` ставит на draft `reasoning: "low"` именно против неё — **сработала всё равно: трижды на draft и один раз на edit, где ручка не выставлена вовсе**; а `min_max_tokens: 16000` у `pro` поднят с 8000 ПОСЛЕ прошлого такого инцидента, чья цена записана рядом в комментарии как 15.5 % счёта — здесь один вызов НА ПОДНЯТОМ флоре сжёг 16 000 токенов и вернул ноль, **16.9 % счёта**. ⭐ Лечение в движке ЕСТЬ и сработало 4 раза из 4 (перевыпуск с удвоенным потолком вернул текст) — путь не рвётся, он ДОРОЖАЕТ вдвое. Вторая точка того же явления: на прогоне 08.09 было 32 % | бэкенд | **скоро** | ручка против мины на ВСЕХ платных стадиях + решение, платит ли читатель за выброшенную попытку | критик чисел прогона A, 11.09 | | 416 | **СОГЛАСИЕ НА ПЕРЕ-ОПЛАТУ, ВЫСТАВЛЕННОЕ НА ВЕСЬ БЮДЖЕТ, ПРИ ФАКТИЧЕСКОЙ ПЕРЕ-ПОКУПКЕ В 78 РАЗ МЕНЬШЕ.** Замер 11.09: на резюме после банк-стопа `runs.accept_rebill_micro = 2 241 631` — то есть согласие пере-оплатить ВЕСЬ бюджет прогона, — тогда как фактически пере-куплено $0.028742. Разрыв **78×**; от пере-покупки спасли чекпойнты, а не потолок согласия. ⇒ промахнись хэши промптов (смена провода, `memory_version`, снапшота), и черновая волна была бы куплена заново — с уже стоящим согласием читателя. Родня `D39.196` п.2а (переплата читателя запрещена): здесь запрет не нарушен, но и не охраняем ничем, кроме везения | платформа+бэкенд | скоро | согласие соразмерное тому, что реально может быть пере-куплено | критик чисел прогона A, 11.09 | -| 417 | ⛔ **ЗАКОН БАНКА НА ПРОВОДЕ НЕ ВИДЕН В ОТГРУЖЕННОМ ТЕКСТЕ, И СПРОСИТЬ БД «ДОЕХАЛ ЛИ ОН» НЕЧЕМ.** Замерено на живом платном прогоне 11.09 и открыто глазами по каждому попаданию: движок сам объявил на стопе майнинга **9 разошедшихся терминов из 69** (`bank-stop.txt`, распределение `spread`: 1→60 · 2→6 · 3→2 · 4→1). Из девяти **четыре отданы ОДНОЙ формой, и это форма терминолога** (`甲等资质` 6 вхождений при трёх конкурентах по 0 — черновик давал «дар цзя», «дарование первого ранга», «талант класса А»); **одна отдана ДВУМЯ** (`开窍大典` ×9 плюс развёрнутая ×1); ⛔ **и четыре отданы одной формой, которая НЕ БАНКОВСКАЯ**: `高脚吊楼` — банк несёт «дом на сваях», в файле **«свайный дом» ×4 и «дом на сваях» ×0**; `家老` — банк «старейшина рода», в файле «старейшины» ×13 без «рода»; `舅母` — банк «жена дяди по матери», в файле «тётя» ×16. ⇒ **`D39.104` (строка на проводе есть ЗАКОН для каждой роли) в этих юнитах не исполнен, и почему — не установлено:** доехал ли банк до промпта редактора, по БД ответить НЕЛЬЗЯ. ⛔ **ИСПР. 11.09 — формулировка уточнена ЗАМЕРОМ, прежняя была неверна: счётчики инъекции ЕСТЬ и они богатые** (`retrieval_state`: `n_exact_hits` · `n_sticky` · `n_ambiguous_flagged` · `n_spoiler_blocked` · `n_evicted` · `n_postcheck_miss` · `injected_ids` JSON, `backend/internal/store/migrate.go:239-255`, и код сам называет их «механизмом, превращающим тихую деградацию в громкую»). **Дефект в КЛЮЧЕ: `PRIMARY KEY (book_id, chapter, chunk_idx)` — строка ОДНА НА ЧАНК, а не на чанк×СТАДИЮ.** Черновая и редакторская волны инъектируются РАЗНЫМИ выборками (черновик — базовый банк без намайненного, редактор — обогащённый), и их следы складываются в одну строку ⇒ вопрос «что видел РЕДАКТОР» неразрешим по построению схемы, а не по отсутствию прибора. ⇒ первый шаг — не «завести счётчик», а **развести ключ по стадии**, и он же дешёвый: таблица пере-считывается детерминированно каждый прогон ($0, матчер бесплатный) и само-восстанавливается на резюме. Замерено РАСХОЖДЕНИЕ формы; место его возникновения — открытый вопрос, и первый шаг к нему $0: счётчик инъекций на редакторской стадии. ⚠ Не путать с рядом **407** (пост-проверка спрашивает присутствие вместо формы на месте): там прибор не видит дрейфа, здесь дрейф ВИДЕН и не объяснён | бэкенд | **скоро** | счётчик подтверждённых инъекций у редактора; затем объяснение четырёх случаев | критик полноты прогона A, 11.09 | +| 417 | ⛔ **ЗАКОН БАНКА НА ПРОВОДЕ НЕ ВИДЕН В ОТГРУЖЕННОМ ТЕКСТЕ, И СПРОСИТЬ БД «ДОЕХАЛ ЛИ ОН» НЕЧЕМ.** Замерено на живом платном прогоне 11.09 и открыто глазами по каждому попаданию: движок сам объявил на стопе майнинга **9 разошедшихся терминов из 69** (`bank-stop.txt`, распределение `spread`: 1→60 · 2→6 · 3→2 · 4→1). Из девяти **четыре отданы ОДНОЙ формой, и это форма терминолога** (`甲等资质` 6 вхождений при трёх конкурентах по 0 — черновик давал «дар цзя», «дарование первого ранга», «талант класса А»); **одна отдана ДВУМЯ** (`开窍大典` ×9 плюс развёрнутая ×1); ⛔ **и четыре отданы одной формой, которая НЕ БАНКОВСКАЯ**: `高脚吊楼` — банк несёт «дом на сваях», в файле **«свайный дом» ×4 и «дом на сваях» ×0**; `家老` — банк «старейшина рода», в файле «старейшины» ×13 без «рода»; `舅母` — банк «жена дяди по матери», в файле «тётя» ×16. ⇒ **`D39.104` (строка на проводе есть ЗАКОН для каждой роли) в этих юнитах не исполнен, и почему — не установлено:** доехал ли банк до промпта редактора, по БД ответить НЕЛЬЗЯ. ⛔ **ИСПР. 11.09 — формулировка уточнена ЗАМЕРОМ, прежняя была неверна: счётчики инъекции ЕСТЬ и они богатые** (`retrieval_state`: `n_exact_hits` · `n_sticky` · `n_ambiguous_flagged` · `n_spoiler_blocked` · `n_evicted` · `n_postcheck_miss` · `injected_ids` JSON, `backend/internal/store/migrate.go:239-255`=`CREATE TABLE IF NOT EXISTS retrieval_state`, и код сам называет их «механизмом, превращающим тихую деградацию в громкую»). **Дефект в КЛЮЧЕ: `PRIMARY KEY (book_id, chapter, chunk_idx)` — строка ОДНА НА ЧАНК, а не на чанк×СТАДИЮ.** Черновая и редакторская волны инъектируются РАЗНЫМИ выборками (черновик — базовый банк без намайненного, редактор — обогащённый), и их следы складываются в одну строку ⇒ вопрос «что видел РЕДАКТОР» неразрешим по построению схемы, а не по отсутствию прибора. ⇒ первый шаг — не «завести счётчик», а **развести ключ по стадии**, и он же дешёвый: таблица пере-считывается детерминированно каждый прогон ($0, матчер бесплатный) и само-восстанавливается на резюме. Замерено РАСХОЖДЕНИЕ формы; место его возникновения — открытый вопрос, и первый шаг к нему $0: счётчик инъекций на редакторской стадии. ⚠ Не путать с рядом **407** (пост-проверка спрашивает присутствие вместо формы на месте): там прибор не видит дрейфа, здесь дрейф ВИДЕН и не объяснён | бэкенд | **скоро** | счётчик подтверждённых инъекций у редактора; затем объяснение четырёх случаев | критик полноты прогона A, 11.09 | | 419 | ⛔⛔ **СТЕММЕР СНИМАЕТ ОДНО ОКОНЧАНИЕ, И ИЗ 18 ПРОМАХОВ ЖИВОГО ПЛАТНОГО ПРОГОНА **11 — ЛОЖНЫЕ**: банковская форма ДОЕХАЛА до читателя в том же юните, склонённая, а пост-проверка её не увидела.** Замерено бэкенд-сессией по-юнитно на копии базы прогона A 11.09 (контроль: 84 показано − 66 последовало = 18, столько же записей в `postcheck_detail`). Механизм один: `TargetStemmer.Stem` снимает РОВНО ОДНО окончание, а двум падежным формам нужны окончания РАЗНОЙ длины ⇒ стеммы выходят разной длины, `SameStem` требует равенства (`backend/internal/lang/stemmer.go:70`=`func (s TargetStemmer) SameStem`), и во всех одиннадцати один стемм — строгий ПРЕФИКС другого с разницей в ОДИН знак («путь»/«пут» · «ветвь»/«ветв» · «крепость»/«крепост» · «злой»/«злог»). Примеры ложных: `正道` «праведный путь» → в тексте «школы праведного пути»; `白家寨` «крепость рода Бай» → «крепости рода Бай» ×2. **Настоящих расхождений 7**, и `家老`/`舅父` из ручного оракула прогона среди них. ⭐ **КЛАСС БЫЛ ПРЕДСКАЗАН, И ЛЕЧЕНИЕ НАЗВАНО В САМОМ ДЕРЕВЕ:** пять из одиннадцати — мягкий знак, исключённый из `decl_suffix` НАМЕРЕННО (`backend/internal/lang/data/target-ru.txt:231`=`the bare SOFT SIGN`, `D39.71`: иначе «Синь»/«Линь» схлопнутся на стеммы «синий»/«линия»), и записка там же говорит — «the soft-sign nominative gap needs seed decl-form enumeration or an **anchor-gated match**, not this global suffix». Пинг стоял; **цена его не была замерена до сегодня**. ⛔ Следствия: (1) число «18 расхождений» завышено в 2.5 раза — эррата 11.09-б; ⚠ **адрес испр. 11.09: числа 18 в теле `D39.247` НЕТ, оно жило в промте пака и в таблице `docs/README.md`, и там же исправлено;** (2) прибор консистентности, переиспользующий банковское равенство, НАСЛЕДУЕТ дыру и врёт примерно в 60 % строк — поэтому пак печатает ДВА числа (по равенству и по якорному послаблению), а разница есть цена стеммера; (3) точность глоссарного чекера **0.067** (ряд 408) — вероятно ТОТ ЖЕ механизм, а не данные сида; проверяется по `fp_ids` ⚠ **11.09: корень шире — ряд 423.** Ненулевые `decl.forms` есть только в рукописных сидах, в машинных выгрузках их НОЛЬ, поэтому на нормальной дорожке продукта одношаговый стеммер — единственное, что работает. | бэкенд | **скоро** | якорное послабление на БОЕВОМ пути `dstFormPresent` — отдельным паком, после замера прибором на размеченном корпусе | пак консистентности, 11.09 | | 420 | ⛔ **`unit_done` ВСТАВЛЕННОЙ ГЛАВЫ НЕ ОБЪЯВЛЯЕТСЯ ВОВСЕ — тихая потеря доставки за швом.** Найдено дизайн-паком перекроя 11.09 при инвентаре чужой стороны: ключ объявления несёт ОРДИНАЛ (`backend/internal/pipeline/events.go`, греп `once_key`), отказ молчалив (греп `outbox`), а `deliveredUnits` считает юнит доставленным. ⇒ при вставке главы читатель не получает объявления о доставке, и никто этого не замечает. **Это не перепокупка, а потеря СОБЫТИЯ**; чинится в движке. ⚠ Единственная настоящая потеря за швом из всего, что дизайн проверил: остальные четыре его подозрения оказались «уже построено» на стороне платформы | бэкенд | скоро | объявление доставки, переживающее вставку главы | дизайн-пак перекроя, 11.09 | | 421 | **ЗАМЕР, РАДИ КОТОРОГО ШЕСТОЙ ВОПРОС СНЯТ С ВЛАДЕЛЬЦА: сколько банк-батчей на боевой книге двигает ТОЛЬКО окно.** Дизайн-пак завёл вопрос о проводе банк-ролей и САМ его снял с владельца доводом «цена меняла знак ТРИЖДЫ — 11 % всегда → ноль → интервал от нуля до всего прохода; число, трижды менявшее знак, владельцу отдавать нельзя». Постановка готова: считать на боевой книге долю батчей, у которых меняется только окно `since_ch`/`until_ch`, и тем закрыть интервал. ⚠ **Ряд заведён 11.09 по находке аудита: и дизайн, и акт `D39.245` объявляли замер «ушедшим в бэклог», а в бэклоге его НЕ БЫЛО** — классический случай «заведено» со слов, без грепа по живому трекеру | бэкенд/полигон | когда-нибудь | доля батчей, двигающих только окно, числом на боевой книге | дизайн-пак перекроя 11.09, заведён аудитом смены | @@ -357,8 +357,8 @@ | 438 | ⛔ **БАНК-РОЛИ ИДУТ МИМО ОБЕИХ ВЕТВЕЙ ЛЕЧЕНИЯ: у черновой стадии лекарство есть, у терминолога нет ни лекарства, ни счётчика.** Обе ветви живут в цикле `runStage` (`backend/internal/pipeline/stagerun.go`, греп `regenerating with less thinking` и `regenerating with a larger budget`), а `terminologist.go` (греп `return r.runAttempt`) зовёт попытку НАПРЯМУЮ. ⛔ Замер прогона B: на попытке 1 классификатор дал `asked=22 answered=4` (`length`, $0.012646) и `asked=24 answered=0` (`empty`, $0.012133) — **42 терма из 66 без машинного типа, оба провальных батча ОПЛАЧЕНЫ, регенерации не было**; у черновой стадии та же мина вылечена 5 раз из 5. ⚠ **Это СТОХАСТИКА, а не регрессия** (сказала сама зона): на резюме тот же классификатор ответил 22/22 · 19/19 · 24/24, у прогона A все три батча чисты. ⚠ И вторая половина — НАБЛЮДАЕМОСТЬ: итоговая строка печатает `classify_batches_dropped=0 unanswered=0`, и это не ложь, а ОТСУТСТВИЕ — счётчик считает батчи, выброшенные БЮДЖЕТОМ (комментарий `terminologist.go` сам предупреждает, что «unanswered читается как вердикт о ТЕРМАХ, когда он отчасти вердикт о ДЕНЬГАХ»), а счётчика доли ОТВЕТОВ нет ни одного; единственный носитель факта — WARN посреди прохода ⚠ **16.09 (`D39.254` п.2):** владелец ожидает у банк-ролей ретрай и отдельный переспрос как штатный механизм, а не как улучшение. Предмет входит в первый банк-пак вместе с N-1. ⛔ **ДОБАВКА 16.09, СВЕРЕНО ДЕРЕВОМ — у петли регенерации есть денежная мина, и её надо заказать вместе с петлёй:** тождество запроса включает индекс попытки и `maxTokens` (`backend/internal/pipeline/stagerun.go:485`=`attemptRequest is the ONE definition`), а проба «уже оплачено» у банк-батча спрашивает ТОЛЬКО попытку 0 с базовым бюджетом (`backend/internal/pipeline/terminologist.go:840`=`paidAfterBurns(st, st.Model`). ⇒ попытка с другим бюджетом пробе НЕВИДИМА, и доккомментарий рядом сам называет исход: ложное «оплачено» пропускает покупку мимо ролевого суб-бюджета, «under nothing but the book ceiling». **Петля обязана расширить пробу на лестницу попыток (attempt × maxTokens), иначе кап пожизненного пула на повторы не действует.** Нашёл советчик-фабл, проверено деревом оркестратором.| бэкенд | **скоро** | банк-роль получает ту же ветвь регенерации, что черновая стадия, ПЛЮС счётчик доли ответов в итоговой строке | прогон B, акт `D39.252` п.9 | | 439 | ⛔⛔ **БАНК КНИГИ НЕДЕТЕРМИНИРОВАН ПО ПОСТРОЕНИЮ: он есть функция ЧЕРНОВИКА, а черновик — стохастический выход модели.** Замер сессии прогона B, пере-снятый оркестратором СВОИМ запросом и сошедшийся дословно: `retrieval_state.banknote_detail` двух прогонов на ОДНИХ И ТЕХ ЖЕ шести чанках, побайтно один исходник (sha среза сверена) и тождественный покупающий конфиг (sha `pipeline-c1.yaml` совпал) — предложений **A 98 · B 93 · общих 74 ⇒ ЖАККАР 0.63**. ⛔ **Расходится не только СОСТАВ, но и ГРАНУЛЯРНОСТЬ ключа:** на `ch3/1` прогон A предлагает СОСТАВНЫЕ (`丙等资质` · `族长家老` · `舅父舅母`), а B — их СОСТАВЛЯЮЩИЕ (`丙等` · `资质` · `族长` · `家老` · `舅母` · `舅父`); на `ch2/0` предложение B — строгое ПОДМНОЖЕСТВО A, беднее на восемь. Отсюда же исчезновение `舅父舅母`, которое встречается в исходнике **14 раз** и есть в банке A при отсутствии в B — частотным порогом это не объясняется. ⛔ **СЛЕДСТВИЕ СТАРШЕ РЯДА 436: консистентность мерится ПО СТРОКАМ БАНКА, а строки между прогонами разные ⇒ у `I1`/`I2` двух прогонов РАЗНЫЕ ЗНАМЕНАТЕЛИ, и они несравнимы не потому, что прибор плох, а по построению.** ⚠ И это прямо задевает цель 6 («воспроизводимый прогон — повторение без сюрпризов»): снапшот и голден стерегут ВХОД, а состав банка в них не заперт. ⚠ Границы замера названы сессией: одна книга, три главы, шесть чанков, ОДНА пара прогонов — установлен РОД расхождения (состав + гранулярность), не его обычная величина; как из предложений собирается финальный банк, не читал никто ⛔ **ПОСЫЛКА «ТОЖДЕСТВЕННЫЕ ВХОДЫ» НЕВЕРНА, снято 12.09 опровергателем и пере-проверено оркестратором.** Совпадение sha `pipeline-c1.yaml` было выдано за тождество входов: `diff -rq` двух фриз-деревьев даёт **52 расходящихся пути** (`chunkrun.go`, `stagerun.go`, `waverun.go`, `disposition.go`, `quality.go`, стеммер), и снапшоты РАЗНЫЕ (`cfd53cc0…` против `fef36e1d…`). ⚠ **Что при этом устояло и держит вывод:** `internal/miner` и `internal/terminology` между фризами **побайтно идентичны** (0 различий), langpack идентичен, `bankParseVersion` совпал, диф `models.yaml` — только комментарии. ⇒ **детерминированная половина не двигалась, двигалась ЧЕРНОВАЯ сторона — а она и производит банкноты.** ⛔ Но ДОЛЯ расхождения, приходящаяся именно на температуру, ЭТОЙ парой прогонов не измерена и измерима быть не может: нужен повтор на ОДНОМ движке, и такого прогона нет. ⚠ И метод числа назвать обязательно: 0.63 — это сумма ПОЧАНКОВЫХ пересечений (74 из 98+93−74); книго-уровневые множества дают 53 из 82 = **0.65**. Вывод переживает оба, число без метода — нет | бэкенд | **скоро** | состав банка воспроизводим при повторе прогона ЛИБО названо и ратифицировано, что банк стохастичен, и тогда числа консистентности сравниваются только ВНУТРИ прогона | прогон B, дописка 12.09 | | 440 | ⛔ **МАЙНЕР ОТСЕКАЕТ ЦЕНТРАЛЬНЫЕ ТЕРМИНЫ КНИГИ ПО ПОСТРОЕНИЮ, А НЕ ПО ПОРОГУ — и это несовпадение ДВУХ РАЗНЫХ ВОПРОСОВ.** Формулировка сессии прогона B, принята дословно: **контраст спрашивает «характерно ли слово для ЭТОГО текста на фоне китайского вообще», а продукт спрашивает «нужна ли слову ОДНА закреплённая передача на всю книгу».** Для `家族` ответ на первый — НЕТ, на второй — ДА. Замер (контроль напечатан: словарь-контраст jieba 0.42.1, **349 046 строк**): `家族` · `族` · `炼制` · `楼阁` · `先机` · `老妈子` — **ЕСТЬ в словаре-контрасте и потому НЕТ в банке ни одного из двух прогонов**, тогда как `方源` · `古月山寨` · `丫头片子` · `怀璧之罪` в словаре отсутствуют и в банк попали. ⇒ **шесть из восьми терминов, на которых читатель поймал разнобой, отсечены ПОТОМУ ЧТО ОНИ ОБЫЧНЫЕ КИТАЙСКИЕ СЛОВА.** ⛔ Цена названа текстом: `族` встречается в срезе **29 раз**, `家族` — **9**, и «род ↔ клан» есть КРУПНЕЙШАЯ находка приоритета №1 за оба прогона. ⚠ Лечится в ПОЛНОТЕ МАЙНИНГА, а не в гейте: энфорсер не молчал — ему не о чем было говорить ⛔⛔ **ПРИЧИННОСТЬ ЭТОГО РЯДА ОПРОВЕРГНУТА 12.09 ЧТЕНИЕМ КОДА — механизм НЕ контраст.** `backend/internal/miner/miner_emit.go`, функция `emissionEligible`: **первым** стоит `if !hasAnyType(c.Types, "name", "place", "title") { return false }`, **вторым** — `if c.Freq < emitMinFreq \|\| subsumed[c.Src] \|\| runeLen(c.Src) < 2`. ⇒ **`族` отсечён БЕЗУСЛОВНО как ОДНОРУННЫЙ**, к контрасту это отношения не имеет; **`家族` отсечён ТИПОМ** — у него 2 руны и freq 9 при `emitMinFreq` 5, то есть он проходит всё, на что ряд указывал, и падает на том, о чём ряд молчал. ⭐ **И настоящая находка ШИРЕ прежней: детерминированный канал по построению эмитирует ТОЛЬКО `name\|place\|title`.** Обычное существительное, несущее в книге (`家族` «род/клан», `炼制` «выплавить»), не может войти в банк этим каналом НИКОГДА — сколько бы раз оно ни встретилось. Это же объясняет, почему строк с провенансом `mined`-без-банкноты **ноль в обоих прогонах**: тип-фильтр так узок, что всё им пропущенное черновик называет и так. ⚠ Прежняя формулировка («ЕСТЬ в словаре-контрасте и ПОТОМУ НЕТ в банке») выдавала корреляцию 6 из 8 за механизм — класс назван и в `CLAUDE.md`. Нашёл опровергатель промта, пере-проверено оркестратором чтением функции | бэкенд | **скоро** | решение владельца: имеет ли право обычное существительное входить в банк детерминированным каналом, и если да — каким признаком, кроме типа | прогон B, §18.1(а) + дописка 12.09 | -| 441 | **Громкий гейт: банк включён, а у цели НЕТ текстов инъекции.** Сегодня это молчание при потраченных деньгах банк-ролей: рендер закона гейтится `HasData()` (`backend/internal/membank/memory.go:746`, `:842`), в `injection.txt` 8 строк данных, все `ru`, и на любой другой цели оба банк-блока рендерятся в 0 байт (замерено `research/34`). Гейт ставится там же, где построенный образец, — до первого платного вызова | бэкенд | скоро | гейт + пин | `research/35` N-4 | -| 442 | Комментарий `backend/internal/pipeline/volume.go:341` обещает «stops loudly», а без `--resnapshot`, пока в книге есть новые юниты, покупка проходит без ошибки (исполнено на $0-стабе) | бэкенд | когда-нибудь | правка комментария под поведение | `research/35`, K1-3 | +| 441 | **Громкий гейт: банк включён, а у цели НЕТ текстов инъекции.** Сегодня это молчание при потраченных деньгах банк-ролей: рендер закона гейтится `HasData()` (`backend/internal/membank/memory.go:746`=`if !tx.HasData()`, `:842`), в `injection.txt` 8 строк данных, все `ru`, и на любой другой цели оба банк-блока рендерятся в 0 байт (замерено `research/34`). Гейт ставится там же, где построенный образец, — до первого платного вызова | бэкенд | скоро | гейт + пин | `research/35` N-4 | +| 442 | Комментарий `backend/internal/pipeline/volume.go:341`=`are then pinned to a superseded one` обещает «stops loudly», а без `--resnapshot`, пока в книге есть новые юниты, покупка проходит без ошибки (исполнено на $0-стабе) | бэкенд | когда-нибудь | правка комментария под поведение | `research/35`, K1-3 | | 443 | Устаревшие комментарии банкового контура: `miner_emit.go:224-227`, `terminologist.go:33`, справка `--verify-bank`, `mining.go:212-213`, `memory.go:819-822`, заголовок `injection.txt:6` про снятую пометку ⟨проверить⟩ | бэкенд | когда-нибудь | сверка комментариев с поведением | `research/35` Б-3 | | 444 | **Ложная атрибуция в коде:** `backend/internal/membank/memory.go:947` пишет «a masculine default when unavoidable, D19.3», а в теле D19 п.3(в) этого нет; D5 п.1 говорит обратное — «не дефолтить в мужской». ⚠ `internal/terminology/classify.go:43` НЕ чинить: там D19.3 пересказан верно. Едет вместе с нотой о том, что делать при невозможной безродовой форме ⚠ **16.09 (`D39.254` п.3):** владелец решил, что делать при невозможной безродовой форме — перестроить фразу, а если литературно не выходит, писать как лучше читается. ⇒ правится и текст данных `injection.txt:12`, не только атрибуция.| бэкенд | скоро | правка атрибуции после ноты | `research/35` Б-4, G-1 | | 445 | ⚠ **ПОЛОВИНА ЗАКРЫТА актом `D39.257`:** мусор (`dst` без букв ВООБЩЕ) больше не едет законом — он падает в `Unanswered`, стережётся пином и мутацией. **ОСТАТОК — отказ ПРОЗОЙ:** механизм построен и инертен до первой фразы в реестре, то есть ряд закрывается вместе со строкой о заполнении реестра. Ниже — исходная формулировка. **Отказ терминолога прозой становится передачей и едет редактору законом.** Неотвеченными считать и `dst` без букв, и отказную фразу (`家族 → «не термин»`, `族长 → «90»` — исполнено). ⚠ Не гигиена, а поведенческая правка на денежном пути: меняет содержимое банка, а с ним `memory_version` и пере-редактуру под согласием. Популяция на A/B нулевая (0 из 69 и 0 из 66 при контроле 69/69 и 66/66), на длинной книге у бюджетного потолка — нет | бэкенд | скоро | разбор ответа роли + пин; решение владельца по В2 | `research/35` Б-5, П3 |