diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index b9570daa..b1c3f176 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -349,14 +349,14 @@ | 296 | **ТОЧЕЧНАЯ ПЕРЕГЕНЕРАЦИЯ ПО ПРОМАХУ ГЛОССАРИЯ — направление владельца 05.09 («или флагать, или перегенерировать проблемный участок»), работы сегодня НЕТ.** Актуаторы ремонта — только `RepairLatinResidue` и `RepairBrokenWord` (`backend/internal/pipeline/repair.go`, греп `defaultRepairClasses`); `glossary_miss` нет ни там, ни в `knownRepairClasses`. Сегодняшние лекарства не точечные и обе дороги: удержать ВЕСЬ фрагмент (гейт) либо править глоссарий с `--resnapshot`, то есть ПЕРЕ-ОПЛАТИТЬ книгу. ⇒ заказ: `glossary_miss` как класс-актуатор — перегенерация участка с подписанной формой. ⚠ Гейтится строкой **295**: без замера точности перегенерация по ложному срабатыванию перепишет верный текст | бэкенд | когда-нибудь (после 295) | новый класс актуатора + пост-инвариант | **D39.205** | | 297 | ✅ **ПЕРВАЯ ПОЛОВИНА ИСПОЛНЕНА ЛЕНДИНГОМ 06.09 (`2f65d1d`, акт `D39.210`): словарь получил четвёртое значение `delimited`**, и spine-EPUB вместе с `\f` в txt больше не выдаются за слово формата. ⛔ **ВТОРАЯ ПОЛОВИНА ТЕПЕРЬ ОБЯЗАТЕЛЬНА, А НЕ ЖЕЛАТЕЛЬНА:** временное правило платформы «`declared` вести как `none`» после лендинга начинает ОТНИМАТЬ ползунок глав у книг, чья структура объявлена настоящим `nav`/NCX — то есть молча недодавать продукт. ⚠ **Якоря прежней редакции этой строки указывали на текст, который пак УДАЛИЛ, а якорный линт этого НЕ ВИДЕЛ** — они были без токена, файл длинный, адрес «разрешался». Живая рабочая строка протухла молча; класс несёт строка **306**, и это его первый пойманный случай в рабочей строке, а не в архиве. ⚠ **Тело прежней редакции СНЯТО 06.09, а не оставлено под шапкой** — оно цитировало рекламу «how much the chapter cut can be trusted» как живую улику, а пак её УДАЛИЛ (`grep -c` → 0), и три его якоря целили в текст, которого нет: словарь уехал, а `:94-95` попал в структуру, введённую дофиксом. То есть строка утверждала про себя, что она первый пойманный случай «якорь протух молча», и одновременно продолжала им БЫТЬ; поймал это строитель пака грепом, линт был зелён. **Что стало вместо снятого:** словарь живёт на `backend/internal/chunk/ingest.go:125-149`=`WHICH PATH drew the chapter boundaries` — четыре значения, и первая же строка комментария теперь говорит про ПУТЬ, а не про доверие. Прежняя двусмысленность («поле отвечает на провенанс, а рекламирует доверие») **устранена паком, а не описана** | бэкенд+платформа | скоро (структурный пак) | недостающее значение на проводе, и `declared` означает ТОЛЬКО объявленную форматом структуру глав | **D39.202**, D39.205 | | 298 | ⛔ **`unit_resolutions` КЛЮЧЕВАНЫ ПОЗИЦИЕЙ, И ЛЮБОЙ ПЕРЕКРОЙ ИХ СНОСИТ ЦЕЛИКОМ.** Ключ `(book_id, chapter, unit, wave)` позиционен по построению, и миграция это прямо объявляет: «keyed by the ENGINE's own ordinals» (`platform/internal/pgstore/migrations/00015_seam_ceiling_and_units.sql:50-56`); событие `unit_done` несёт номер главы и ординал юнита, синк пишет их как есть (`platform/internal/pgstore/sink.go:249`). Потому при перекрое платформа ОБЯЗАНА удалять решения — иначе они перевесятся на чужую главу. Сегодня цена нулевая: решения есть только у стенд-книги, и это окно закрывается с каждой новой платной книгой. ⇒ разрешать `(chapter, unit)` в идентификатор ПРИ ПРИЁМЕ события, пока дерево у платформы на руках, и хранить идентификатор. Платформенная половина структурного пака: миграция плюс синк. ⚠ Этой строкой НЕ закрывается одноразовая перечеканка всех id при смене смысла `cutTag` — это другой предмет и другая цена | бэкенд+платформа | скоро (структурный пак) | решения переживают перекрой, не переклеиваясь на чужую главу | **D39.202** | -| 299 | ⛔ **ИМЯ В GO-КОММЕНТАРИИ, КОТОРОЕ НИ ВО ЧТО НЕ РАЗРЕШАЕТСЯ, — И НИЧТО ЭТОГО НЕ ЛОВИТ.** ⭐ **ЗАМЕР 05.09, полностью меняющий форму ДОРОГОЙ половины (той, что сверяет имя с каталогом мутаций): она обязана писать ФАЙЛ, а не читать `stdout`.** Полный прогон каталога дал: несущих верхнеуровневых тестов **189** из 1213 тест-функций движка (**15,5 %** батареи; ⚠ испр. 06.09 автором замера: было «194 / 16,0 %» — его парсер резал вывод по запятой, а пять подтестов несут запятую В ИМЕНИ, и круглые «16,0 %» получились именно из пяти фантомов. ⚠ Числитель и знаменатель разной природы: 1213 считает только `^func Test`, а один из 189 — `Fuzz`; честно 188/1213 либо 189/1219, но не 189/1213), и **139 записей из 197 пинятся РОВНО ОДНИМ тестом** — то есть краснота атрибутируется однозначно, и долговременная запись «какие тесты несущие» реально получается. ⛔ **Но тул обрезает список имён на четырёх и печатает `+N more` — так усечены 9 записей из 197, и механизм, читающий `stdout`, насчитал бы 178 вместо 194 И НЕ УЗНАЛ БЫ ОБ ЭТОМ.** Это тот же класс «прибор ответил на свой вопрос»: величина ВЫЧИСЛЯЕТСЯ сегодня, но в поток приезжает УСЕЧЁННОЙ. ⇒ вход механизма — `-logs`, не вывод. Точный список имён воспроизводится пере-прогоном каталога с `-logs`; сохранять его отдельным артефактом не нужно. Два живых носителя, найдены верификацией 05.09 (`D39.207` п.3): **(а)** `backend/internal/runevents/runevents.go:187`=`not to a screen` цитирует **СТАРЫЙ** путь — `docs/BACKEND_MONEYSTOP_SESSION_PROMPT.md`, строка 78 (записано БЕЗ формы якоря намеренно: это УЛИКА мёртвой ссылки, а не ссылка; в форме якоря линт вечно краснел бы на предмете самой записи) — и удалён именно он (промт уведён в архив коммитом `f71c4dc`, тем же, что писал акт), а цитируемая фраза ЖИВА в архивной копии и стоит там на строке **82**. ⚠ **Испр. 06.09: клауза была заландена ПЕРЕВЁРНУТОЙ** — называла удалённым архивный файл, который цел, и приписывала комментарию цитату, которой в нём нет. Механика ошибки записана отдельно, она повторяемая: сплошная замена строки якоря при пере-наведении попала в ОПИСАНИЕ дефекта, где тот же путь был УЛИКОЙ, а не ссылкой, и превратила описание поломки в описание её отсутствия. Нашёл верификатор `textmachine-main-e6` контролем закрытия своих же находок; ⚠ приёмочный столбец строки при этом сформулирован ВЕРНО и поймал бы дефект независимо от прозы — то есть major в описании, не в гейте; **(б)** `backend/internal/config/internal_call.go:11`=`builds a Stage outside the loader` и `backend/internal/pipeline/live_reprobe_test.go:14`=`the single-egress guard` называют сторожами тесты `TestSyntheticStageSeamIsSingle` и `TestProviderEgressSeamIsSingle`, которых в дереве НЕТ — инварианты при этом реально сторожатся вет-анализаторами `StageSeam`/`EgressSeam` (`backend/internal/archguard/archguard.go:80,377`=`StageSeam, RequestSeam, EgressSeam`), то есть гарантия цела, а назначенный ей сторож ВЫДУМАН. Замер верификатора: из 2026 упомянутых в Go имён `Test…` определены 2012; из восьми неразрешающихся четыре — не дефекты (надгробия и иллюстрация), два — мелочь, два — эти. ⇒ **дешёвая половина открытого вопроса `D39.202` п.9: не «доказал ли каталог, что названный тест умеет падать» (дорого, требует интеграции с `tmmutate`), а «РАЗРЕШАЕТСЯ ли названное имя вообще» — это греп, и сегодня он находит два дефекта.** ⚠ `counts.py --lint` этого класса не видит по построению: он читает доки, а не код. ⚠⚠ **ДВА ПРАВИЛА, БЕЗ КОТОРЫХ ГЕЙТ ВЫКЛЮЧАТ НА ВТОРОЙ ДЕНЬ** (принесены верификатором вместе с находкой): **(1)** разрешением считается и ПРЕФИКС — доки и комментарии цитируют имя усечённым, как греп-цель; **(2)** НАДГРОБИЯ не дефект — комментарий, прямым текстом говорящий «удалён/заменён», законен, как и иллюстративное имя в примере. Замер, обосновывающий оба: из восьми неразрешающихся имён СЕГОДНЯ четыре — надгробия и иллюстрация, два — усечение в шапке над функцией, и лишь два — настоящие выдуманные сторожа. Гейт без этих правил краснеет 8 из 8, то есть врёт вчетверо, и его погасят — ⚠⚠ **и ЗАМЕР 06.09 показал, что для указателей `путь:строка` в Go арифметика ХУЖЕ, а потолок дешёвой половины НИЖЕ, чем считалось.** Всего таких указателей **36**; настоящий дефект среди них **ОДИН** (`runevents.go:187`, уже назван выше). Остальное: **5 целят ВНЕ репозитория** (стандартная библиотека Go, `yaml.v3`, jieba) — внутрирепозиторный линт их проверить не может в принципе; **~7 разрешаются НЕОДНОЗНАЧНО** — голое имя файла имеет от двух до восьми одноимённых кандидатов. ⇒ наивный гейт покраснел бы примерно **двенадцать раз ложно при одном настоящем дефекте**. ⛔ **ДВА правила записать ДО заказа, иначе гейт врёт впятеро и его выключат:** **(1)** внешняя цель — НЕ дефект (путь, не разрешающийся в дереве и опознаваемый как чужой, исключается); **(2)** голому имени нужна КОНВЕНЦИЯ разрешения — сперва каталог цитирующего файла, затем модуль, затем корень: без неё «разрешается» не определено для семи из тридцати шести, и гейт судит о том, чего не умеет спросить. ⚠ И третье, важнее обоих: **НИ ОДИН из 36 не несёт токена**, поэтому ДОРОГАЯ половина упирается не в инструмент, а в то, что сверять нечего; дешёвая остаётся верным первым шагом, но её потолок теперь измерен — **один дефект на тридцать шесть**. а гейт умирает не отключением, а вытеснением из привычки (`D39.202` п.3-бис) | бэкенд | скоро | грепом: имя `Test…` и `<док>.md:<строка>` в Go разрешаются, иначе красно | **D39.207**, D39.202 | +| 299 | ⛔ **ИМЯ В GO-КОММЕНТАРИИ, КОТОРОЕ НИ ВО ЧТО НЕ РАЗРЕШАЕТСЯ, — И НИЧТО ЭТОГО НЕ ЛОВИТ.** ⭐ **ЗАМЕР 05.09, полностью меняющий форму ДОРОГОЙ половины (той, что сверяет имя с каталогом мутаций): она обязана писать ФАЙЛ, а не читать `stdout`.** Полный прогон каталога дал: несущих верхнеуровневых тестов **189** из 1213 тест-функций движка (**15,5 %** батареи; ⚠ испр. 06.09 автором замера: было «194 / 16,0 %» — его парсер резал вывод по запятой, а пять подтестов несут запятую В ИМЕНИ, и круглые «16,0 %» получились именно из пяти фантомов. ⚠ Числитель и знаменатель разной природы: 1213 считает только `^func Test`, а один из 189 — `Fuzz`; честно 188/1213 либо 189/1219, но не 189/1213), и **139 записей из 197 пинятся РОВНО ОДНИМ тестом** — то есть краснота атрибутируется однозначно, и долговременная запись «какие тесты несущие» реально получается. ⛔ **Но тул обрезает список имён на четырёх и печатает `+N more` — так усечены 9 записей из 197, и механизм, читающий `stdout`, насчитал бы 178 вместо 194 И НЕ УЗНАЛ БЫ ОБ ЭТОМ.** Это тот же класс «прибор ответил на свой вопрос»: величина ВЫЧИСЛЯЕТСЯ сегодня, но в поток приезжает УСЕЧЁННОЙ. ⇒ вход механизма — `-logs`, не вывод. Точный список имён воспроизводится пере-прогоном каталога с `-logs`; сохранять его отдельным артефактом не нужно. Два живых носителя, найдены верификацией 05.09 (`D39.207` п.3): **(а)** `backend/internal/runevents/runevents.go:187`=`not to a screen` цитирует **СТАРЫЙ** путь — `docs/BACKEND_MONEYSTOP_SESSION_PROMPT.md`, строка 78 (записано БЕЗ формы якоря намеренно: это УЛИКА мёртвой ссылки, а не ссылка; в форме якоря линт вечно краснел бы на предмете самой записи) — и удалён именно он (промт уведён в архив коммитом `f71c4dc`, тем же, что писал акт), а цитируемая фраза ЖИВА в архивной копии и стоит там на строке **82**. ⚠ **Испр. 06.09: клауза была заландена ПЕРЕВЁРНУТОЙ** — называла удалённым архивный файл, который цел, и приписывала комментарию цитату, которой в нём нет. Механика ошибки записана отдельно, она повторяемая: сплошная замена строки якоря при пере-наведении попала в ОПИСАНИЕ дефекта, где тот же путь был УЛИКОЙ, а не ссылкой, и превратила описание поломки в описание её отсутствия. Нашёл верификатор `textmachine-main-e6` контролем закрытия своих же находок; ⚠ приёмочный столбец строки при этом сформулирован ВЕРНО и поймал бы дефект независимо от прозы — то есть major в описании, не в гейте; **(б)** `backend/internal/config/internal_call.go:11`=`builds a Stage outside the loader` и `backend/internal/pipeline/live_reprobe_test.go:14`=`the single-egress guard` называют сторожами тесты `TestSyntheticStageSeamIsSingle` и `TestProviderEgressSeamIsSingle`, которых в дереве НЕТ — инварианты при этом реально сторожатся вет-анализаторами `StageSeam`/`EgressSeam` (`backend/internal/archguard/archguard.go:80,377`=`StageSeam, RequestSeam, EgressSeam`), то есть гарантия цела, а назначенный ей сторож ВЫДУМАН. Замер верификатора: из 2026 упомянутых в Go имён `Test…` определены 2012; из восьми неразрешающихся четыре — не дефекты (надгробия и иллюстрация), два — мелочь, два — эти. ⇒ **дешёвая половина открытого вопроса `D39.202` п.9: не «доказал ли каталог, что названный тест умеет падать» (дорого, требует интеграции с `tmmutate`), а «РАЗРЕШАЕТСЯ ли названное имя вообще» — это греп, и сегодня он находит два дефекта.** ⚠ `counts.py --lint` этого класса не видит по построению: он читает доки, а не код. ⚠⚠ **ДВА ПРАВИЛА, БЕЗ КОТОРЫХ ГЕЙТ ВЫКЛЮЧАТ НА ВТОРОЙ ДЕНЬ** (принесены верификатором вместе с находкой): **(1)** разрешением считается и ПРЕФИКС — доки и комментарии цитируют имя усечённым, как греп-цель; **(2)** НАДГРОБИЯ не дефект — комментарий, прямым текстом говорящий «удалён/заменён», законен, как и иллюстративное имя в примере. Замер, обосновывающий оба: из восьми неразрешающихся имён СЕГОДНЯ четыре — надгробия и иллюстрация, два — усечение в шапке над функцией, и лишь два — настоящие выдуманные сторожа. Гейт без этих правил краснеет 8 из 8, то есть врёт вчетверо, и его погасят — ⚠⚠ **и ЗАМЕР 06.09 показал, что для указателей `путь:строка` в Go арифметика ХУЖЕ, а потолок дешёвой половины НИЖЕ, чем считалось.** Всего таких указателей **36**; настоящий дефект среди них **ОДИН** (`runevents.go:187`, уже назван выше). Остальное: **5 целят ВНЕ репозитория** (стандартная библиотека Go, `yaml.v3`, jieba) — внутрирепозиторный линт их проверить не может в принципе; **~7 разрешаются НЕОДНОЗНАЧНО** — голое имя файла имеет от двух до восьми одноимённых кандидатов. ⇒ наивный гейт покраснел бы примерно **двенадцать раз ложно при одном настоящем дефекте**. ⛔ **ДВА правила записать ДО заказа, иначе гейт врёт впятеро и его выключат:** **(1)** внешняя цель — НЕ дефект (путь, не разрешающийся в дереве и опознаваемый как чужой, исключается); **(2)** ⛔ **у голого имени файла SOUND-ДЕФОЛТА НЕТ — гейт обязан считать его НЕПРОВЕРЯЕМЫМ (skip), а не угадывать.** ⚠ Прежняя редакция этого пункта предлагала конвенцию «сперва каталог цитирующего файла, затем модуль, затем корень» — **и она ОПРОВЕРГНУТА 06.09 её же автором**: для дока, цитирующего КОД, каталог цитирующего файла систематически неверен, потому что рядом с доком лежат другие ДОКИ. Контрпример — `PROGRESS.md:71` → `README.md:95`: конвенция даёт `docs/README.md` (41 строка) и вердикт «протух», а автор имел в виду `backend/README.md`, где строка 95 **та же, что в минуту написания**. В дереве `README.md` четырнадцать штук. ⇒ гейт с конвенцией краснел бы именно на ЖИВЫХ якорях. Хочешь проверяемости — пиши путь или токен. ⚠ И третье, важнее обоих: **НИ ОДИН из 36 не несёт токена**, поэтому ДОРОГАЯ половина упирается не в инструмент, а в то, что сверять нечего; дешёвая остаётся верным первым шагом, но её потолок теперь измерен — **один дефект на тридцать шесть**. а гейт умирает не отключением, а вытеснением из привычки (`D39.202` п.3-бис) | бэкенд | скоро | грепом: имя `Test…` и `<док>.md:<строка>` в Go разрешаются, иначе красно | **D39.207**, D39.202 | | 300 | ⛔ **РЕПО-ШИРОКИЙ СТОРОЖ НЕ ДОТЯГИВАЕТСЯ ДО СОСЕДНЕГО ВОРКРИ НА ДРУГОЙ ВЕТКЕ — и стенд от этого УЖЕ СГНИЛ.** ⚠ Замер 05.09, вторая половина: снять пин манифеста стенд-конфигами удалось для **ОДНОГО исходника из восьми**; семь падают, и ни одно падение не про структурный пак. Причины — ретированные в `main` вещи, живые в дереве `polygon`: плейсхолдер `{{genre}}` (ретирован `D39.138` п.2в) — в моём дереве `grep -rln '{{genre}}' backend/prompts/` ПУСТ, в соседнем живёт в ШЕСТИ файлах промтов, и падают на нём 6 конфигов из 8; поля `stm_depth`/`overlap_tokens`, которых больше нет в `config.ContextAssembly` — 1 из 8; ключи `mined_delta:`/`mined_rejects:` (ретированы `D39.156` п.3) — **17 конфигов из 31**. ⭐ **Тест `genre_retired_test.go:46` предсказал этот случай дословно** («should be caught by the repository that retired it, not by a paid run») — и поймали его действительно бесплатно, но **сторож не дотянулся до соседней ветки**, где ретированное живо. ⇒ общая форма дефекта: ретирование, проведённое репо-широким гейтом, НЕ распространяется на воркри другой ветки, и стенд тихо остаётся в прошлом. **ЧАСТНЫЙ СЛУЧАЙ ТОГО ЖЕ КОРНЯ — ДАННЫЕ ПАР-ПАКА В ДВУХ ВЕТКАХ.** Замер бэкенд-сессии 05.09 по 31 конфигу `book.yaml` стенда 蛊真人: у **20** `langpack_root` указывает в СОСЕДНЕЕ РАБОЧЕЕ ДЕРЕВО `/home/ubuntu/projects/textmachine` — а оно на ветке **`polygon`**; у 9 — в `main`; 2 не объявляют его вовсе. Пока каждая ветка собирается из своего дерева, расхождения не видно. ⚠ **Структурный пак (`CHAPTER_STRUCTURE_SESSION_PROMPT.md`) выносит `marker`/`units` из `zh-ru/heading.txt` в данные ИСТОЧНИКА и учит `parseHeading` ОТВЕРГАТЬ эти ключи с переходным сообщением** ⇒ старый файл, оставшийся на `polygon`, станет невалидным, и двадцать конфигов не загрузятся вовсе (загрузка пар-пака FAIL-LOUD). **Выстрелит НЕ при лендинге в `main`, а в момент, когда полигон вольёт `main` в свою ветку** — то есть цена назначена чужой зоне и в чужой момент. ⇒ заказ: перед мержем полигон либо снимает `marker`/`units` из своего `heading.txt`, либо переводит `langpack_root` своих конфигов на `main`. ⚠ Второе дешевле и, возможно, правильнее по существу: **каталоги `configs/langpacks` в обоих деревьях сегодня БАЙТ-В-БАЙТ идентичны** (`diff -rq` пуст, sha256 `heading.txt` совпадают), а комментарии в конфигах, утверждающие «`ru/` есть только здесь», ПРОТУХЛИ — класс строки 299. То есть двадцать конфигов смотрят в чужую ветку по причине, которой больше нет | полигон | скоро (до мержа main в polygon) | конфиги грузятся после мержа, либо `langpack_root` переведён | **D39.207** | | 301 | ⛔ **`--max-units` НЕ УМЕЕТ СКАЗАТЬ «ДОРАБОТАЙ ПЕРЕНЕСЁННОЕ И НЕ НАЧИНАЙ НОВОГО», И ЧЕЛОВЕК ЗА СВОИ ДЕНЬГИ ПОЛУЧАЕТ НЕ ТУ РАБОТУ.** Флаг бюджетирует N НОВЫХ стартов, а carried-юниты (задрафченные прошлым прогоном и не отгруженные) движок допускает СВЕРХ гранта, до N штук: `granted()` их не считает (`backend/internal/pipeline/volume.go:396-407`), и собственный тест движка это фиксирует — под `--max-units 1` при одном carried выходит `Delivered=2, Carried=1` (`volume_test.go:721`). ⇒ прогон, оборвавшийся МЕЖДУ волнами (стоп на подпись банка, деплой, краш), при респауне получает `--max-units = заказ − доставлено`, и движок оплачивает ЧЕРНОВИКИ глав, которых человек НЕ ЗАКАЗЫВАЛ, раньше редактуры заказанных. ⚠ **Это НЕ перерасход** — `--ceiling-usd` держит сумму; ломается ровно обещание «вы купили N»: за те же деньги приходит другая работа. Выразить нужное платформа не может по построению: вычесть carried значит обнулить грант, а ноль для движка = «без границы» (`planVolume` при `MaxUnits <= 0` возвращает `nil`, `volume.go:321`). ⇒ заказ: словарь скоупа обязан различать «сколько НОВЫХ начинать» и «сколько всего допускать». Живая половина **эрраты 03.09-а** (перенос юнита вне гранта, семантика НЕ ратифицирована, слово владельца «подумаем»), но со следствием более острым, чем записано в ноте: там был перерасход единиц, здесь — подмена предмета покупки | бэкенд+платформа | скоро | флагом выражается «ничего нового не начинать»; пин на респауне между волнами | **D39.207** | | 302 | **РЕЗКА xhtml ПО ЯКОРЯМ `nav`: текст цел, ГРАНУЛЯРНОСТЬ ТЕРЯЕТСЯ.** Массовая форма EPUB — несколько глав в ОДНОМ документе, разделённых якорями, и `nav` объявляет на них отдельные цели. Структурный пак 1 такие цели СХЛОПЫВАЕТ: одна глава вместо трёх, текст не теряется (формулировка бэкенд-сессии 05.09: «текст цел, гранулярность потеряна»). ⚠ **Ложь при этом уже не едет** — тем же паком ратифицировано: схлопнулась хоть одна цель ⇒ книга НЕ `declared`, а `delimited`, плюс счёт `targets collapsed: N` в WARN. То есть провенанс честен, а разрез груб. ⇒ заказ: резать документ по якорям целей. ⛔ **Почему НЕ сделано паком 1 и почему это дорого:** это единственное место, трогающее `extractXHTML`, у которого **четыре пина байт-паритета** (`ingest_test.go:323, 359, 423, 467` — void-теги, несбалансированный ruby, raw-text-элементы, голый амперсанд), и ошибка там ТИХАЯ. Заходить в него в конце пака, ради гранулярности, при живом гейте разреза — плохой размен. ⚠ Строкой закрывается ГРАНУЛЯРНОСТЬ, а не провенанс: продать такую книгу по главам платформа и так не предложит | бэкенд | когда-нибудь (после пака 2) | книга с тремя якорями в одном документе даёт три главы; четыре пина байт-паритета целы | **D39.207** | | 303 | **НЕ-CJK ПУТЬ ПОСТРОЕН НАПОЛОВИНУ: чанкер безъюнитную грамматику УЖЕ УМЕЕТ, ингест по ней НЕ РЕЖЕТ.** `detectChapterUnit` итерирует `UnitOrdered` и без юнита возвращает 0 ⇒ грамматика вида «Kapitel 12» / «Chapter 12» (маркер + номер, БЕЗ юнита) книгу не режет; при этом `matchHeaderLine` чанкера юнита не требует и на это есть живой тест (`TestStripHeadingGenericLatinMarker`). То есть заголовок такой книги движок умеет СНЯТЬ, но не умеет по нему РАЗРЕЗАТЬ. ⇒ ревью-вопрос проекта «заработает ли пара, которой в репо НЕТ, без правки Go» закрыт для языков С ЮНИТОМ (доказано сквозным тестом `TestANewLanguageCutsFromItsDataFileAlone`: без файла корейская книга — одна глава, с `ko/structure.txt` — три, разница только в данных) и **ОТКРЫТ для языков без юнита**. Это названный ВХОД в пак 2, а не открытие по ходу. ⚠⚠ **ГРАНИЦА ЗАМЕРА, названная самой зоной и записанная здесь в её настоящей силе:** утверждение проверено **ПО КОДУ и ПО ТЕСТУ НА ФИКСТУРЕ**; **живая не-CJK книга через ингест НЕ прогонялась**. «На живой книге» под этой строкой НЕ стоит, и ссылаться на неё как на замер живого поведения нельзя | бэкенд | скоро (вход пака 2) | безъюнитная грамматика режет книгу; замер на живой не-CJK книге | **D39.207** | | 304 | ⚠ **РИСК NULLABLE ДЕНЕЖНЫХ КОЛОНОК — НЕ ТЕОРЕТИЧЕСКИЙ, А ОТЛОЖЕННЫЙ, и это вскрыла ЛОЖНАЯ ПРИЧИНА в отчёте зоны.** Платформенная зона объяснила чистый `sqlc diff` тем, что новые колонки лежат «в таблицах, для которых sqlc не держит запросов» — **догадка, и неверная**: `queries/credits.sql` и `queries/exports.sql` читают `books`, `chapters` и `runs`. Верная причина: ни один из этих запросов не выбирает `*` и не называет новых колонок, поэтому сгенерированный код не двигается (греп по `*.sql.go` — ноль вхождений). ⇒ **`books` ЛЕЖИТ в мире sqlc**, значит риск NULLABLE денежных колонок наступит на ПЕРВОМ же запросе, который их выберет, — не «никогда», а «пока не спросили». ⚠ Нашла это зона САМА, четвёртым заходом по прямому вопросу владельца «нет ли чего, что ты умолчала»; ни один гейт поймать это не мог — сторожат поведение и форму, а не то, что отчёт о себе ГОВОРИТ (`D39.202` п.9) | платформа | скоро | первый запрос, выбирающий денежные колонки, не роняет генерацию | **D39.208** | | 305 | ⚠ **`make check` ПЛАТФОРМЫ ПРИ ТАЙМАУТЕ ПАКЕТА ПЕЧАТАЕТ ЗАГОЛОВОК `--- FAILURES ---` И НИЧЕГО ПОД НИМ.** Замер верификатора 06.09: пакет упёрся в дефолтный десятиминутный таймаут (`FAIL … 600.251s`), секция отказов ПУСТА, ни одного `--- FAIL:` — оператор получает «check упал» **без единого имени**. ⇒ «go test вышел ненулём» и «тест упал» — разные события, а прибор их не различает. ⭐ Это платформенное ЗЕРКАЛО урока, ради которого каталог мутаций движка завёл отдельный вердикт `INCONCLUSIVE`: там различение построено, здесь нет. ⚠ Сам прогон, вскрывший это, верификатор объявил **INCONCLUSIVE и в выводы НЕ взял** — он шёл под нагрузкой тринадцати агентов, и приписывать причину «частично настроенное окружение краснит батарею» отказался сам, назвав это той же ошибкой, которую ловит | платформа | скоро | таймаут пакета называет пакет и причину, а не пустую секцию | **D39.207** | -| 306 | ⛔ **СЕМЬСОТ ЯКОРЕЙ БЕЗ ТОКЕНА НЕПРОВЕРЯЕМЫ, И 59% ИЗ НИХ УЖЕ НЕ УКАЗЫВАЮТ ТУДА, КУДА УКАЗЫВАЛИ.** Замер Fable 5 06.09 (выборка 80, seed фиксирован, метод — blame-триаж: коммит последней правки СТРОКИ ДОКА → цель на том коммите → сравнение): цела **31** · уехала **25** · переписана либо исчезла **22** · не судится 2. ⇒ **59% (±11 п.п.)**. ⚠ Линт сверяет по токену 202 и честно печатает «без токена — несверяемы: 780», но ЗАГОЛОВОК «0 проблемных якорей» читается как «всё цело» — прибор отвечает на свой вопрос, а читают его как ответ на другой (`D39.202`). ⚠ **Дрейф сосредоточен там, где он ОЖИДАЕМ:** 34 из 47 сдвинувшихся — в ЗАМОРОЖЕННЫХ отчётах, чьи тела улика по `D23.3` и обязаны описывать дерево НА ДАТУ; их чинить НЕЛЬЗЯ, им нужен штамп HEAD в шапке. **Опасны 13 в ЖИВЫХ носителях**, и часть из них — в ШАПКЕ журнала, то есть в первом, что читает сессия. ⇒ заказ, тремя шагами и без велосипедов: **(1)** заголовок линта печатает ПОКРЫТИЕ («сверено N · несверяемы M · из них с изменившейся целью — K»), чтобы гейт перестал врать формой; **(2)** ОДНОРАЗОВАЯ механическая миграция тем же скриптом: «цела» → дописать токен автоматически, «уехала» с единственным попаданием → пере-навести, «исчезла» → список на руки; замороженным отчётам вместо пере-наводки штамп коммита и исключение из линта; **(3)** правило «токен при касании» остаётся удержанием линии; **(4)** ⛔ **грамматика УЛИКИ — `path:line@`**, которую линт сверяет через `git show :path`, а НЕ против HEAD (~20 строк в `counts.py`). Без неё автор, цитирующий мёртвый указатель как ПРЕДМЕТ записи, вынужден снимать форму якоря руками — я так и сделал 06.09 и объявил это в тексте, но это подгонка ЗАПИСИ под модель прибора, а не починка модели: **законно как последнее исключение, незаконно как привычка**. Та же форма закрывает и ЗАМОРОЖЕННЫЕ отчёты, которым сегодня штамп HEAD стоит прозой (`research/31`/`32`). ⚠ Сегодня без (2) правка любого дока превращается в игру «бей крота»: каждое касание вскрывает соседний голый якорь. ⚠⚠ **ПЕРЕ-ЗАМЕР 06.09 ГРАММАТИКОЙ САМОГО ЛИНТЕРА — МАСШТАБ ОКАЗАЛСЯ В РАЗЫ МЕНЬШЕ, И ЭТО МЕНЯЕТ ЗАКАЗ.** По 117 живым докам: **214 якорей с токеном** (это и есть те, про кого «0 проблемных») и **1776 без токена**; за конец файла целят 66, однозначно разрешаются 9, и семь из девяти — ВНЕШНИЕ цитаты (стандартная библиотека Go, чужой фронтовый код). ⇒ **настоящих внутрирепозиторных протухших якорей ДВА:** `05-decisions-log.md:21` → `seeding.go:143-153` (в файле 128 строк) — **в ШАПКЕ журнала, которую сессия читает первой** — и `PROGRESS.md:71` → `README.md:95` (в файле 40 строк). ⚠ Прежняя оценка «59 % на выборке 80» и агентские «42 %» / «19 из 32 мертвы» **НЕ ПОДТВЕРДИЛИСЬ**: обе получены наивным резолвингом — голое `mining.go:40` из подкаталога искали от корня, не находили и объявляли мёртвым. Автор пере-замера получил тем же способом «31 мёртвый из 36» и не понёс их, потому что стал сверять расхождение. ⚠ И у метода есть ложные срабатывания: две находки этого же прохода я проверил руками — одна («`STACK.md:11` исчезло») оказалась ЛОЖНОЙ, цель жива и точна | оркестратор | скоро | линт печатает покрытие; голых якорей в ЖИВЫХ доках нет | **D39.202** | +| 306 | ⛔ **СЕМЬСОТ ЯКОРЕЙ БЕЗ ТОКЕНА НЕПРОВЕРЯЕМЫ, И 59% ИЗ НИХ УЖЕ НЕ УКАЗЫВАЮТ ТУДА, КУДА УКАЗЫВАЛИ.** Замер Fable 5 06.09 (выборка 80, seed фиксирован, метод — blame-триаж: коммит последней правки СТРОКИ ДОКА → цель на том коммите → сравнение): цела **31** · уехала **25** · переписана либо исчезла **22** · не судится 2. ⇒ **59% (±11 п.п.)**. ⚠ Линт сверяет по токену 202 и честно печатает «без токена — несверяемы: 780», но ЗАГОЛОВОК «0 проблемных якорей» читается как «всё цело» — прибор отвечает на свой вопрос, а читают его как ответ на другой (`D39.202`). ⚠ **Дрейф сосредоточен там, где он ОЖИДАЕМ:** 34 из 47 сдвинувшихся — в ЗАМОРОЖЕННЫХ отчётах, чьи тела улика по `D23.3` и обязаны описывать дерево НА ДАТУ; их чинить НЕЛЬЗЯ, им нужен штамп HEAD в шапке. **Опасны 13 в ЖИВЫХ носителях**, и часть из них — в ШАПКЕ журнала, то есть в первом, что читает сессия. ⇒ заказ, тремя шагами и без велосипедов: **(1)** заголовок линта печатает ПОКРЫТИЕ («сверено N · несверяемы M · из них с изменившейся целью — K»), чтобы гейт перестал врать формой; **(2)** ОДНОРАЗОВАЯ механическая миграция тем же скриптом: «цела» → дописать токен автоматически, «уехала» с единственным попаданием → пере-навести, «исчезла» → список на руки; замороженным отчётам вместо пере-наводки штамп коммита и исключение из линта; **(3)** правило «токен при касании» остаётся удержанием линии; **(4)** ⛔ **грамматика УЛИКИ — `path:line@`**, которую линт сверяет через `git show :path`, а НЕ против HEAD (~20 строк в `counts.py`). Без неё автор, цитирующий мёртвый указатель как ПРЕДМЕТ записи, вынужден снимать форму якоря руками — я так и сделал 06.09 и объявил это в тексте, но это подгонка ЗАПИСИ под модель прибора, а не починка модели: **законно как последнее исключение, незаконно как привычка**. Та же форма закрывает и ЗАМОРОЖЕННЫЕ отчёты, которым сегодня штамп HEAD стоит прозой (`research/31`/`32`). ⚠ Сегодня без (2) правка любого дока превращается в игру «бей крота»: каждое касание вскрывает соседний голый якорь. ⚠⚠ **ПЕРЕ-ЗАМЕР 06.09 ГРАММАТИКОЙ САМОГО ЛИНТЕРА — МАСШТАБ ОКАЗАЛСЯ В РАЗЫ МЕНЬШЕ, И ЭТО МЕНЯЕТ ЗАКАЗ.** По 117 живым докам: **214 якорей с токеном** (это и есть те, про кого «0 проблемных») и **1776 без токена**; за конец файла целят 66, однозначно разрешаются 9, и семь из девяти — ВНЕШНИЕ цитаты (стандартная библиотека Go, чужой фронтовый код). ⇒ **настоящий внутрирепозиторный протухший якорь ОДИН** (испр. 06.09 автором замера): `05-decisions-log.md:21` — **в ШАПКЕ журнала, которую сессия читает первой**; цель нашлась `git log -S` по тексту якоря и пере-навёдена с токеном. ⚠ **Второй кандидат (`PROGRESS.md:71` → `README.md:95`) оказался АРТЕФАКТОМ РАЗРЕШЕНИЯ ИМЕНИ, а не дефектом** — и он же служит уликой к правилу (2) строки 299. ⭐ **Дешёвый метод для ведра «исчезла», исполненный, а не описанный:** `git log -S'<текст якоря>' -- <док> | tail -1` даёт коммит, ВВЕДШИЙ якорь; `git show <коммит>:<цель>` — что стояло в ту минуту ДОСЛОВНО; `git grep` — сегодняшний адрес. Угадывания нет ни на одном шаге. ⚠ Прежняя оценка «59 % на выборке 80» и агентские «42 %» / «19 из 32 мертвы» **НЕ ПОДТВЕРДИЛИСЬ**: обе получены наивным резолвингом — голое `mining.go:40` из подкаталога искали от корня, не находили и объявляли мёртвым. Автор пере-замера получил тем же способом «31 мёртвый из 36» и не понёс их, потому что стал сверять расхождение. ⚠ И у метода есть ложные срабатывания: две находки этого же прохода я проверил руками — одна («`STACK.md:11` исчезло») оказалась ЛОЖНОЙ, цель жива и точна | оркестратор | скоро | линт печатает покрытие; голых якорей в ЖИВЫХ доках нет | **D39.202** | | 307 | ⛔ **СПАН ПОД ПОДПИСЬЮ «СКОЛЬКО ЗАКАЗАНО» — ЛОЖЬ ЯРЛЫКОМ, И ЧЕСТНАЯ ФОРМА ДРУГАЯ.** Для заказа, выраженного ЗНАКАМИ, `Run.ordered_chapters` несёт не заказанное, а **СПАН** — сколько глав заказ ЗАТРАГИВАЕТ, считая главу затронутой независимо от того, куплена ли она целиком (`pricing.QuoteUnits` ставит `Chapters: max(chapters, 1)`, вызывающий передаёт `chaptersSpanning`, и сам код называет это «coarser of the two figures»). ⚠ **Замер зоны:** заказ на ОДИН юнит из четырёх в первой главе даёт `1` — **сто процентов завышения на самом дешёвом заказе**, то есть на том, которым сервис пробуют впервые. ⇒ число под именем «ordered chapters», превышающее заказанное, — ложь ярлыком, сколь угодно последовательная. ⭐ **Разбор, снявший спор:** нужда, которую зона защищала («видно, докуда дошли деньги»), настоящая, а носитель неверный — «докуда» есть **ПОЗИЦИЯ** («достигает главы N»), а не **КОЛИЧЕСТВО** («2 главы»), и позиция у платформы УЖЕ ЕСТЬ (`Quote.ThroughChapter`, `BookOrder.ThroughUnitID`). ⚠ Спан ≠ позиция для заказа-продолжения: спан считает затронутые от текущей точки, позиция — ординал. ⇒ **честная форма:** `ordered_chapters: null` для символьного заказа (зеркало `delivered_chapters: null` — «заказано не в главах») + аддитивно `ordered_characters` (эхо запроса) + позиция ординалом, если экрану нужно «докуда». Это смена формы (поле становится нуллабельным) ⇒ минор. ⚠ **Цена смены почти пуста и это ЗАМЕРЕНО:** `ordered_chapters` вошёл в канон только `75ae6f0`, до него в файле НОЛЬ вхождений — клиент 0.10.0 поля не видел никогда, а `ceiling_chapters` тот же минор уже отвергает с 400. ⚠ Поведение менять НЕ требуется: политика «заказ выражен в знаках и меряется в них насквозь» верна и остаётся. До лендинга формы канон описывает ДЕФЕКТ с ⛔-пометкой, а не дизайн | бэкенд+платформа | скоро | `ordered_chapters` нуллабелен, размер заказа эхом, позиция отдельно | **D39.208** | | 308 | **ВИД ПРОГОНА ЗАКОДИРОВАН НУЛ�ём, И ЭТО ЕДИНСТВЕННАЯ ЕГО МЕТКА ВО ВСЕЙ ПЛАТФОРМЕ.** Явного признака ре-прохода у `LiveRun` НЕТ: `Resnapshot` не годится — он ставится и ОБЫЧНОМУ продолжению после правки банка, а валидация `OrderedChapters == 0 && !Resnapshot` читается «ноль допустим только под пере-снапшот», то есть ре-проход ⇒ resnapshot, а не наоборот. ⇒ **вид прогона различается ЗНАЧЕНИЕМ денежного поля**, и держится это на двух вызовах `max(n, 1)` (`pricing.QuoteUnits`, `runs.chaptersSpanning`). ⚠ **Опасность растёт после лендинга формы заказа** (строка 307): снаружи символьный заказ станет `null`, и единственное место, где `max(n,1)` что-то значит, уходит ВНУТРЬ — следующий читатель колонки (отчёт, CLI) напечатает спан под старым словом и не узнает об этом. ⇒ заказ: **явный признак вида** (`RePass`) вместо кодирования вида нулём. ⛔ **Рантайм-пояса `== 0 && OrderedUnits == nil` ставить НЕЛЬЗЯ** — символьный заказ не может записать ноль ни при каких данных, а гейт от несуществующего состояния завтра прочтут как свидетельство, что состояние бывает. Честная защита до рефакторинга — **ПИН инварианта** «символьный заказ на один юнит пишет в колонку 1, никогда 0» (замер у зоны уже есть — сто процентов завышения на самом дешёвом заказе; норма `D39.209` требует превратить замер в пин) плюс одно предложение в комментарий колонки. ⚠ Это рефакторинг в ДЕНЕЖНОМ коде без дефекта сегодня — отдельным паком, не минором формы заказа | платформа | когда-нибудь (после строки 307) | вид прогона читается признаком, а не значением денежного поля | **D39.208** | | 309 | ⛔ **ГЕЙТ ВЕРСИИ КОНТРАКТА СВЕРЯЕТ ЧИСЛА, А НЕ ФОРМЫ — И ПОТОМУ НЕ ВИДИТ ГЛАВНОГО РАСХОЖДЕНИЯ.** `TestTheAnnouncedContractVersionIsTheOneTheCanonRatified` сверяет константу сборки с `info.version` канона. Пока эти два числа равны, гейт ЗЕЛЁН — **даже если провод и документ описывают РАЗНЫЕ формы**. Замерено 06.09 трижды за сутки: канон 0.11.0 объявлял полосу «in chapters», а сборка слала юниты; обещал `ordered_chapters: 0` для символьного заказа, а приезжал спан 2; перечислял три значения словаря, а движок слал четыре. **Ни одно из трёх гейт не покраснил**, потому что число совпадало. ⇒ заказ: сверять ПОЛЯ схемы с проводом, а не только версию. ⭐ **Дешёвая форма названа Fable 5 и уже наполовину оплачена нормой:** тест платформы пишет РЕАЛЬНЫЕ ответы в `examples/*.json` (по действующему правилу «фикстуры снимать реальным бинарём»), канон несёт их в `examples:`, линт сравнивает — тогда фраза «both report 0» умерла бы о файл примера, а не о верификатора через сутки. Прозу это не ловит, ЗНАЧЕНИЯ — ловит. ⚠ Вторая половина той же нормы — **авторство**: утверждение канона о ЗНАЧЕНИИ пишет ЗОНА из своих пинов и называет пин в сообщении коммита; оркестратор ратифицирует текст, а не сочиняет его. Три сегодняшние лжи в каноне написаны тем, у кого не было прогона | оркестратор+платформа | скоро | поля схемы сверяются с проводом порождёнными примерами | **D39.208** | diff --git a/docs/architecture/05-decisions-log.md b/docs/architecture/05-decisions-log.md index 9ee71be6..acfdbc07 100644 --- a/docs/architecture/05-decisions-log.md +++ b/docs/architecture/05-decisions-log.md @@ -18,7 +18,7 @@ > ⚠ **Эррата 27.08-е (D39.159 п.8, финал): `PD-398` ЗАКРЫТА — у построенного гейта появился пин.** `selftest_tail_vocab()` гоняется на каждом `--check`, четыре утверждения, проверен ПОСАДКОЙ трёх мутаций самого гейта (все три пойманы, базовая линия молчит). ⚠ Первая редакция пина молчала на одной из трёх: утверждение про вес проверяло `cells()`, а мутация меняет то, чем пользуется `register()`. Пин, проверенный одним прогоном вместо посадки, — ровно тот класс, который эта строка описывает; поймано только потому, что посадку сделал. Регистр 96 → 95 открытых. > ⚠ **Эррата 27.08-ж (D39.160 п.2) — ошибка ОРКЕСТРАТОРА, найденная исполнителем пака.** Нота утверждала, что после сноса отменённой двери «канон и деплой СОВПАДУТ точно», а промт минора (§3.2-бис) — что счётчики `pending_decisions`/`complete` «навсегда нули». **Верно по ПУТЯМ, неверно по ПОЛЯМ:** проекция `GET /bank` продолжает их слать, и это не нули — `bankCountsTx` (`platform/internal/pgstore/readmodel.go:330`) считает `proposed`-строки, о чём говорит её собственный комментарий. Клиент 0.5.0 лишние поля игнорирует, но аллоулист-норма нарушена до монтажа (2в). Носитель — `PD-399`. ⚠ Контрактная сессия принесла это ПИНГОМ по §11 промта, вместо того чтобы тихо подогнать работу под неверную посылку; это и есть поведение, которого норма требует. > ⚠ **Эррата 27.08-з (D39.156, состав пункта 2в): «воркер решений → глагол перед возобновлением» СНЯТ — посылка изменилась.** Пункт писался, когда двери в контракте не было и подразумевалось НАКОПЛЕНИЕ: платформа копит решения у себя и скармливает их движку перед `resume`. С дверью канона 0.5.0 накопления не существует — правка ПРИМЕНЯЕТСЯ в момент подачи, и гарантия «до возобновления» у синхронной формы СИЛЬНЕЕ воркерной: применено прежде, чем клиент получил `200`. Проверено исполнением с обеих сторон: движок на стопе ВЫХОДИТ (`cmd/tmctl/main.go:95`, код 3), флок не-блокирующий и отпускается ядром на выходе процесса (`store/store.go:187`), между стопом и возобновлением живого процесса на проекте нет — запинено `pipeline/bankchain_test.go:62-64`; кап 5000 решений выведен ИМЕННО из синхронности («the call stops fitting the caller's timeout»). Остаётся не воркер, а пер-книжная сериализация в обработчике. `Resume` «с решениями как они есть» не тронут. -> ⚠ **Эррата 28.08-и (D39.165 §3, размер мины) — ошибка ОРКЕСТРАТОРА, найденная опровергателем промта P10.** Нота утверждает: «первый же ПРОДОЛЖАЮЩИЙ прогон после первой же правки банка УПАДЁТ». **Переоценено.** Гард снапшота стреляет по СУЩЕСТВУЮЩЕМУ джобу (`backend/internal/pipeline/stagerun.go:47` — `EnsureJob` создаёт джоб стадии в момент, когда стадия впервые исполняется), а правка в ГЛАВНОМ окне — стоп подписи `awaiting_bank` — двигает edit-снапшот, когда edit-джобов ЕЩЁ НЕТ: возобновление создаёт их свежими, и гард молчит. Драфт-волна mined-строк не видит вовсе (`seeding.go:143-153`). **Дефект СТОИТ, но его триггер уже: правка, сделанная ПОСЛЕ появления edit-джобов** — пауза потолком посреди редактуры и ДОЧИТАННАЯ книга. ⚠ Срочность при этом НЕ падает: флагманский случай продукта («поправил имя героя в дочитанной книге») — ровно тот, где edit-джобы существуют, то есть мина бьёт именно по нему. **Цена ошибки была бы прямой:** репро на потоке `awaiting_bank` показало бы ЗЕЛЕНЬ без фикса, и пак мог быть отозван как мнимый. Промт P10 §4.2 исправлен: репро обязано фиксировать состояние «edit-джоб существует ДО правки». +> ⚠ **Эррата 28.08-и (D39.165 §3, размер мины) — ошибка ОРКЕСТРАТОРА, найденная опровергателем промта P10.** Нота утверждает: «первый же ПРОДОЛЖАЮЩИЙ прогон после первой же правки банка УПАДЁТ». **Переоценено.** Гард снапшота стреляет по СУЩЕСТВУЮЩЕМУ джобу (`backend/internal/pipeline/stagerun.go:47` — `EnsureJob` создаёт джоб стадии в момент, когда стадия впервые исполняется), а правка в ГЛАВНОМ окне — стоп подписи `awaiting_bank` — двигает edit-снапшот, когда edit-джобов ЕЩЁ НЕТ: возобновление создаёт их свежими, и гард молчит. Драфт-волна mined-строк не видит вовсе (`backend/internal/pipeline/bankmaterialize.go:317`=`if row.Source == "mined"` (⚠ адрес испр. 06.09: код ПЕРЕЕХАЛ вместе с переименованием файла, поэтому линт угадать его не мог — нашёл `git log -S` по тексту якоря)). **Дефект СТОИТ, но его триггер уже: правка, сделанная ПОСЛЕ появления edit-джобов** — пауза потолком посреди редактуры и ДОЧИТАННАЯ книга. ⚠ Срочность при этом НЕ падает: флагманский случай продукта («поправил имя героя в дочитанной книге») — ровно тот, где edit-джобы существуют, то есть мина бьёт именно по нему. **Цена ошибки была бы прямой:** репро на потоке `awaiting_bank` показало бы ЗЕЛЕНЬ без фикса, и пак мог быть отозван как мнимый. Промт P10 §4.2 исправлен: репро обязано фиксировать состояние «edit-джоб существует ДО правки». > ⚠ **Эррата 28.08-к (D39.165 §3 + решение оркестратора о глава-полосе) — ДВЕ ошибки, обе найдены широким самопроходом платформенной сессии, обе доказаны исполнением.** **(1) Посылка «смета УЖЕ публикуется в `status --json`» верна только ПОСЛЕ свёртки.** `bank-apply` пишет только ФАЙЛЫ решений, а `status` считает ре-билл от СОХРАНЁННОГО глоссария (`backend/internal/pipeline/status.go:962`=`seed-FILE edit` ⚠ (адрес испр. 06.09: цитата УЕХАЛА, не исчезла; в теле `D39.165` она осталась по прежнему адресу — тело ноты не переписывается, D23.3)``, `projectStoredMemory` — его собственный комментарий: «A seed-FILE edit not yet re-run is NOT reflected here… that drift surfaces on the next translate's re-seed»). Свёртка происходит внутри СЛЕДУЮЩЕГО `translate`, поэтому сразу после правки движок отвечает `units=0`/`drift=false`. Следствие: продажа «затронуто N юнитов» и холд от сметы В ТЕКУЩЕМ ШВЕ НЕДОСТИЖИМЫ — для них нужен движковый глагол «свернуть банк и оценить ВНЕ translate», которого нет. **(2) Решение оркестратора «полоса пере-прохода — в ГЛАВАХ» ОТМЕНЯЕТСЯ: его посылка опровергнута.** Я рассудил, что $0-репин двигает полосу, потому что идёт через тот же `resumeFromChunkStatus`, — и не проверил анонс. Движок анонсирует юнит ОДИН РАЗ на жизнь книги (announce-once, `backend/internal/pipeline/events.go:49,143-162`=`announce-once keys`), пере-проход не ре-анонсирует ни репины, ни пере-переводы ⇒ `done` остался бы НУЛЁМ навсегда. Это ровно тот класс, от которого предостерегает памятка «не выводить из соседнего механизма, не проверив свой». ⚠ **Что при этом НЕ отменяется:** запрет класть ЮНИТЫ в поле, объявленное в главах, стоит — но объявленная в каноне «одна единица работы» запретом не является, потому что она НЕ молчаливая. > ⚠ **Эррата 29.08-а (D39.172, две строки, объявленные заведёнными) — ошибка ОРКЕСТРАТОРА №19.** Тело ноты дважды утверждает «Заведено строкой» / «строка заведена» — про `Touch`, выбрасывающий `RowsAffected`, и про пересборку `tmctl` в рецепте стенда. **На момент ратификации ни одной из этих строк не существовало:** последняя строка регистра платформы была `PD-430`, и проверка грепом по `Touch|RowsAffected|пересбор` давала только совпадения слов в чужих строках. Утверждение о будущем записано как о свершившемся — ровно тот класс, который эта же смена ловила у сессий трижды. **СНЯТА 29.08: строки заведены зоной — `PD-431` (`Touch`) и `PD-432` (пересборка `tmctl`), `PD-423` получил вторую точку. Проверено грепом по регистру.** Тело ноты не переписывается (D23.3). ⚠ Сюда же третий пункт того же абзаца: `PD-423` предписано ПЕРЕ-ПРОВЕРИТЬ (у сессии `sqlc` тест зелёный в трёх прогонах), и пометки в строке регистра тоже нет. > ⚠ **Эррата 30.08-б (D39.165 §2 и §3, две находки платформенной сессии P12).** (а) **Якорь протух:** §2 цитирует фразу канона о `chapters_done` по `openapi.yaml:1542-1545` — текст уехал на **`1554-1559`**, по прежним строкам сейчас `RejectReason`. Сама цитата верна дословно; нота не битая, битым стал только номер (класс строки бэклога 219). (б) **§3 назвал живое САМОПРОТИВОРЕЧИЕ канона и не дал ему носителя:** правка банка «takes effect on the NEXT run» (`openapi.yaml:504`) против «finished work is not bought twice» плюс подъём потолка только у ПРИОСТАНОВЛЕННОЙ книги (`:590`) — у дочитанной книги следующего прогона купить нечем, поэтому принятая правка умирает молча. Ни в едином бэклоге, ни в регистре платформы строки не было (проверено грепом по обеим фразам) — **заведена строка бэклога 241** 30.08. ⚠ Класс — «названо в теле ноты и не получило карриера»; норма приёмки требует строку ТЕМ ЖЕ лендингом, здесь она не легла два дня.