Add the chapter-boundary pack built on the measured seam, and retarget the drifted anchors across the notes, the experiments and the research files

This commit is contained in:
heaven 2026-09-18 19:38:01 +03:00
parent 0594a85c20
commit 595a8ab46b
16 changed files with 465 additions and 63 deletions

View file

@ -0,0 +1,402 @@
# Промт бэкенд-сессии: ГРАНИЦА ГЛАВЫ — ФАКТ КНИГИ, А НЕ ДОГАДКА ДВИЖКА
> Выдан оркестратором очереди №23. Пак **$0**. Зона записи — `backend/`.
> Ты не коммитишь: дерево передаётся оркестратору на лендинг.
>
> ⛔ **Это НЕ пак «починить осколки английской книги».** Осколки — симптом, и самый дешёвый из трёх.
> Предмет пака — **кто и на каком основании решает, где начинается глава**.
---
## 1. Какая проблема и что решит твой результат
Движок переводит книги мультиагентным пайплайном. Пары языков — ДАННЫЕ, не код; ревью-вопрос по умолчанию:
«заработает ли пара, которой в репо ещё НЕТ, без правки Go?».
Скоро пойдёт платный холодный прогон двух книг: около 20 глав китайской и равный по токенам срез английской.
**Слово владельца, под которым выдан этот пак:** «цель — не бежать поскорее приступить к холодному
прогону, а подготовить бэкенд так, чтоб этот холодный прогон исполнить **максимально правильно**. Сколько
паков и бэклога нам для этого нужно закрыть, не очень важно. Халтурить не надо, подгонять тоже ничего не
надо, спиливаем долг из бэкенда, реализуем актуальный, нужный, чистый архитектурно код».
**Что сломано, замерено мною по книге прогона (пере-снимешь своим прибором — все числа воспроизводимы $0).**
Целей навигации **71**: шесть ярлыков частей (`BOOK ONE``BOOK SIX`) и **65 ярлыков глав** вида
`Chapter XIX. A Taste of Home`. Издательский конвертер разрезал страницу оглавления на отдельные файлы и
объявил навигационную цель на каждый осколок ⇒ **62 цели из 71 указывают во ВТОРУЮ половину spine** (позиции
целей 8…124 при 126 документах, медиана 89), тогда как проза лежит в первой.
**Тела целей, разбиение полное и сумма сходится с 71:** **60** побуквенно равны своему ярлыку · **7** коротки,
но ярлыку НЕ равны — два титула частей (`BOOK ONE`+`DEATHSEEKER`, `BOOK FOUR`+`THIS BLACKENED VEIL`) и пять
осколков оглавления, у которых вторая строка есть имя СЛЕДУЮЩЕЙ части (`Chapter XIX. A Taste of Home` +
`Book Three. To Kill a King`) · **4** несут прозу (`BOOK TWO/THREE/FIVE/SIX`, 1482 тыс. знаков).
**А настоящих глав в книге — 84, и это первое, что я ставил неверно.** Открывает каждую **ДВА подряд абзаца
одного класса**: римское числительное, затем имя прописными, затем проза другим классом. Замер: абзацев, чей
текст есть голое римское числительное, — **84** (81 в шести частях и 3 в открывающей секции), все в одном и
том же классе; контроль, доказывающий, что прибор читал книгу, — абзацев с классом в ней **11 397**.
⚠ И мой первый замер дал **3** вместо 84, потому что регексп требовал `class` первым атрибутом, а у
большинства первым стоит `id`. **Отрицательный замер здесь особенно дёшев и особенно лжив — печатай
контрольную величину рядом.**
**ГЛАВНОЕ, и ради этого пак переписан.** Ярлык навигации «Chapter IV. Unforgiven» есть СКЛЕЙКА ровно того,
что проза держит двумя абзацами — `IV` и `UNFORGIVEN`. ⇒ **навигация несёт правильные ИМЕНА и неправильные
АДРЕСА; проза несёт правильные АДРЕСА и расщеплённое имя.** Ни один источник не лжёт целиком, и ни одного не
хватает в одиночку.
**Отсюда — чего в этом паке делать НЕЛЬЗЯ, и я сам чуть этого не заказал:** нельзя объявить навигацию
врущей и выбросить её. Прежняя редакция промта говорила «настоящих глав шесть» и «навигация о заголовках не
знает» — **оба утверждения ложны**, и сессия, получив 84, сверила бы их с «шестью» и решила, что верное
правило неверно.
⇒ Задача — не «поймать мусор эвристикой» и не «отвергнуть навигацию», а **склеить два источника**: АДРЕС
берётся у грамматики, ИМЯ — у того, кто его несёт. Твой результат: английская книга режется по своим
настоящим главам, каждая получает имя, и достигнуто это ДАННЫМИ книги, а не веткой в Go.
---
## 2. Зона записи и git
Пишешь только в `backend/`. Чужие зоны читать можно, править нельзя. Исключение — своя секция в
`docs/PROGRESS.md`. Ты не коммитишь: дерево передаётся на лендинг. В дереве могут работать другие сессии —
их незакоммиченные файлы не трогай.
**НИКОГДА не читай `.env` и `.env.*`** — там боевые ключи.
---
## 3. Карта чтения — пять позиций, и она ЗАКОН
**Код-факты добываешь САМА: адреса ниже — отправные точки, а не истина.**
1. `CLAUDE.md` — канон, цели, гардрейлы.
2. `backend/docs/CHAPTER_STRUCTURE_DESIGN.md` — дизайн-оф-рекорд предмета, **2062 строки**. ⛔ Читай **§7.1-бис · §7.2 · §7.3 · §7.4 · §8.1 · §8.2** и шапку. ⚠ **§9 и §11 НЕ читай:** там тринадцать пинов
БОЛЬШОГО перекроя с миграциями и вопросы владельца по нему — §4.5 выносит его из пака, и чтение втянет тебя
в предмет, которого тебе не заказывали. **Статус документа — «ПРЕДЛОЖЕНИЕ, НЕ РАТИФИЦИРОВАНО»**: четыре
круга опровержения, сходимости нет; предметная часть устояла, механика исполнения — нет. ⇒ ссылаться на
него как на решённое НЕЛЬЗЯ; брать оттуда форму — можно и нужно.
3. `backend/internal/chunk/epubtoc.go` — разбор навигации и группировка документов.
4. `backend/internal/lang/structure.go` + `backend/internal/chunk/ingest.go` — грамматика заголовка и её
потребители.
5. `docs/BACKLOG.md`, ряды **283 · 302 · 303 · 201 · 284**.
---
## 4. Работа
### 4.0 ⛔ КОРЕНЬ: ДВА ПУТИ РЕШАЮТ ГРАНИЦУ ГЛАВЫ ПО-РАЗНОМУ И НЕ СПРАШИВАЮТ ДРУГ ДРУГА
Я ставил задачу дважды и дважды неверно: сперва обошёл предмет срезом, потом искал, какой эвристикой ловить
мусор. Верная постановка пришла от владельца одной фразой — «у нас есть статический код, который смотрит,
глава ли это и как она названа, там как-то это всё должно склеиваться», — и замер её подтвердил.
**Замер, пере-снимешь своим прибором.** Диспетчер ингеста получает грамматику источника и отдаёт её **только
одному из двух путей**:
- `backend/internal/chunk/ingest.go:243`=`func ingestTXT(p, encoding, sourceLang string, st *lang.SourceStructure)`
— грамматику **получает**;
- `backend/internal/chunk/ingest.go:678`=`func ingestEPUB(p string, im *lang.InlineMarkup)` — **не получает
вовсе**, только инлайновую разметку.
⇒ **Статический код, который умеет сказать «это глава и вот её имя», построен и работает — на одном пути из
двух.** На пути EPUB его никто не спрашивает: там границу единолично назначает навигация издателя. Отсюда
**71 «глава» вместо 84 настоящих**.
⛔ **А вот пустой детерминированный заголовок сюда НЕ относится, и я едва не свалил его в ту же кучу.**
Он пуст по ДРУГОЙ причине, и твой пак его не заполнит: шаблон берётся из пар-данных
(`backend/internal/chunk/chunker.go:131`=`An absent TEMPLATE`), а у английской пары файла нет вовсе —
в `backend/configs/langpacks/en-ru/` лежат `inline-markup.txt` и `terminology.txt`, и всё
(у `zh-ru` `heading.txt` есть). ⇒ **либо назови «детерминированный заголовок для en — не результат этого
пака», либо закажи `en-ru/heading.txt` ДАННЫМИ** и скажи, как это трётся о ряды 201 и 284. Чего нельзя —
оставить его в причинной цепи: тогда отчёт пообещает то, чего правка не делает.
⭐ **И ЭТОТ ПАК ЕСТЬ «ПАК 2» ПО РЯДУ 303 — объявляю прямо, потому что без этого ты упрёшься в ратифицированный
запрет.** `D39.224` п.7 велит «ось „обязательность юнита" вне пака 2 не трогать (носитель — строка 303)», а сам
ряд 303 называет себя «названным ВХОДОМ в пак 2». ⇒ **запрет снимается ИМЕННО ЭТИМ паком, и ось трогать
можно.** Предмет 303: `detectChapterUnit` итерирует `UnitOrdered` и без юнита возвращает 0, поэтому грамматика
вида «Chapter 12» (маркер + номер, без юнита) книгу не режет — хотя чанкер такой заголовок СНЯТЬ уже умеет.
Форма лечения набросана в дизайне §8.2 (доминанта над ФОРМАМИ заголовка, «без юнита» — последний кандидат);
она НЕ ратифицирована, и ты вправе предложить другую с доводом.
**А вот «283 и 303 — один шов» было моей натяжкой, и её сними.** Это РОДСТВЕННЫЕ, но разные предметы: 303 —
текстовый резак ингеста; 283 — БИЛДЕР ВЫДАЧИ (`backend/internal/bookfile/`), делающий главу из каждого
документа spine у читалки человека. Общее у них — «грамматика не участвует там, где решают границу», и этого
довода достаточно; выдавать их за одну строку значит обещать закрыть 283, чего этот пак не делает.
### 4.1 РОВНО ТАК: грамматика участвует на ОБОИХ путях, а навигация остаётся источником ИМЁН
**Предмет пака — склеить существующие механизмы, а не построить третий.** Грамматика даёт АДРЕС границы,
навигация даёт ИМЯ. Ни новой эвристики, ни порога, ни ветки по языку.
**РЕШИ САМА** форму проводки и то, как два источника сводятся в один ответ. **Жёстко задаю четыре правила, и
каждое — следствие замера, а не вкус:**
**(а) ДОКУМЕНТ, ИСЧЕРПАННЫЙ СВОИМ ЯРЛЫКОМ, — НЕ ГЛАВА, НО ИМЯ ОН НЕСЁТ.** Это тождество, а не порог: тело,
равное ярлыку, прозы не содержит по построению. **60 из 71.** ⚠ И его имя не выбрасывается — оно
единственное место, где «IV» и «UNFORGIVEN» уже склеены в «Chapter IV. Unforgiven».
**ОСТАТОК НАЗВАН ЧИСЛОМ, И ПРЯТАТЬ ЕГО НЕЛЬЗЯ: строгое тождество снимает 60, а ПЯТЬ осколков переживут**
у них вторая строка несёт имя следующей части. **«Почти-тождество» я запрещаю**: порога не заводить. Решай по
роду — либо правило формулируется так, что ловит и эти пять (например «каждая строка тела есть ярлык
навигации»), либо остаток НАЗЫВАЕТСЯ числом и принимается осознанно. ⛔ Чего нельзя — сдать «сделано» и
получить 89 глав вместо 84.
**(б) ТАМ, ГДЕ НАВИГАЦИЯ НЕ ПОСТАВИЛА ЦЕЛИ, ГРАНИЦУ СТАВИТ ГРАММАТИКА.** Настоящие начала глав живут внутри
прозы и навигационных якорей не имеют вовсе; сегодня их не видит никто. ⚠ **Формулировать это как «навигация
молчит» — ошибка:** она называет 65 глав поимённо, она лишь целит не туда.
**(в) ИМЯ БЕРЁТСЯ У ТОГО, КТО ЕГО НЕСЁТ.** Грамматика, взятая одна, даст `title_raw = "IV"` — то есть ХУЖЕ
сегодняшнего, потому что имя в прозе стоит СЛЕДУЮЩИМ абзацем. ⇒ **сшивка обязана назвать, откуда каждая из 84
глав получает имя**, и это входит в приёмку. Вход ряда **284** и §7.1-бис дизайна (title_raw обязан
заполняться любым новым детектором) — читай их до стройки.
**(г) ТИТУЛ ЧАСТИ ОБЯЗАН ОСТАТЬСЯ ГРАНИЦЕЙ, И СЕГОДНЯ ЕГО СПАСАЕТ СЛУЧАЙНОСТЬ.** Два титула уцелели лишь
потому, что несут подзаголовок (`BOOK ONE`+`DEATHSEEKER`). **Титул без подзаголовка был бы тождеством**, и
правило (а) сняло бы границу ЦЕЛОЙ ЧАСТИ — молча, при зелёной батарее. Для «пары, которой в репо ещё нет» это
класс, а не частность. ⇒ **пин ровно на эту форму**: тождественный титул, за которым идут документы без
навигационных целей, ⇒ граница СОХРАНЯЕТСЯ.
**ГРАНИЦЫ, КОТОРЫЕ Я СТАВЛЮ ЖЁСТКО:** порога в знаках в Go **не заводить** · список служебных ролей
**не расширять** (узость намеренна: `backend/internal/chunk/epubtoc.go:327`=`func isServiceRole(role string) bool`) ·
ветки по паре или языку **не появляется ни одной**.
**И ОДНО, ЧТО ПРОМТ ОБЯЗАН БЫЛ РЕШИТЬ И НЕ РЕШАЛ — ИСХОД ДОКУМЕНТА, НА КОТОРОМ ГЛАВУ НЕ ОТКРЫЛИ.** Прежняя
редакция писала «не потерять ни знака» и тут же требовала переписать докблок
`backend/internal/chunk/epubtoc.go:289`=`NO DOCUMENT IS DROPPED HERE` — это ДВА РАЗНЫХ исхода, и я их склеил.
Замер: группировка приклеивает любой неграничный документ к ПРЕДЫДУЩЕЙ группе, а осколки стоят ПОСЛЕ всей
прозы ⇒ «просто не открывать главу» отправит 65 строк оглавления в хвост последней части, и они будут
переведены и отгружены читателю — та же трата, что в ряду **283**, только в другой главе.
**РОВНО ТАК: документ-тождество ИСКЛЮЧАЕТСЯ из чтения, а не приклеивается**, с названной причиной и
размером; инвариант сохранения получает это третье состояние; докблок переписывается тем же движением и
называет условие, при котором вывод держится. ⚠ Судьбу документа, у которого нет навигационной цели и который
сегодня приклеивается к последней части, назови отдельной строкой отчёта.
**РОВНО ТАК по видимости:** рядом с существующими счётчиками аномалий (`unresolved`, `collapsed`, `attached`)
появляется свой, и число доезжает до человека.
### 4.2 ГРАММАТИКА КОНКРЕТНОЙ КНИГИ ПРИЕЗЖАЕТ ЕЁ ДАННЫМИ
Сегодня грамматика читается **только** из пар-данных языка
(`backend/internal/lang/structure.go:142`=`func LoadSourceStructure(root, sourceLang string)`), лестница из
трёх ступеней, книжной ступени нет. А грамматика заголовков КОНКРЕТНОГО файла есть свойство **конверсии
издателя**, то есть книги, а не письменности: тот же роман у другого издателя размечен иначе.
**И вот здесь я ошибся, проверь сама и не повтори за мной.** Я написал было «дом уже ратифицирован —
книжный оверлей» (`backend/internal/config/book.go:105`=`LangpackExtend string`,
`backend/internal/lang/langpack.go:413`=`func LoadWithOverlay(root, src, tgt, overlayRoot string)`). **Это
переоценка: существующий оверлей такой файл ОТКАЖЕТ.** Он сливает только файлы манифеста, и его собственная
ошибка это говорит — `backend/internal/lang/langpack.go:463`=`an overlay merges only the source/pair manifest files`;
`structure.txt` не входит ни в `backend/internal/lang/langpack.go:283`=`var srcFiles = []string{`, ни в
`backend/internal/lang/langpack.go:290`=`var pairFiles = []string{"palladius.txt"`. Ратифицирован он для
частного канона, не для грамматики.
**РОВНО ТАК по дому:** книжная ступень у ЛЕСТНИЦЫ ГРАММАТИКИ
(`backend/internal/lang/structure.go:142`=`func LoadSourceStructure(root, sourceLang string)` — сегодня три
ступени, книжной нет), со СВОИМ отпечатком через
`backend/internal/lang/structure.go:127`=`func structureFingerprint(origin string, b []byte) string`. **НЕ через
`LoadWithOverlay` и НЕ полем брифа.** Довод денежный: поле брифа уходит в хеш брифа,
оттуда в КАЖДЫЙ хеш запроса, и вдобавок рендерится модели. Грамматике границ ехать туда незачем — ей место в
КРОЕ, а крой уже фолдит отпечаток структуры.
**РЕШИ САМА,** что грамматика обязана уметь для этой книги. Приоры, каждый опровергается замером:
безмаркерный заголовок (сегодня маркер требуется) · класс римских числительных (сегодня арабские и CJK) ·
класс римских числительных (сегодня арабские и CJK) · «абзац, чей текст есть голое числительное» ·
«имя главы — СЛЕДУЮЩИЙ абзац того же класса».
⛔ **Приор «центрированный абзац» я СНИМАЮ, и вот почему — иначе он столкнул бы тебя лбом с §4.5.**
Вычисленный стиль виден только внутри обходчика `backend/internal/chunk/ingest.go:1155`=`func extractXHTML`, а
§4.5 запрещает его трогать: там четыре пина байт-паритета и ошибка ТИХАЯ. Наружу сегодня выходит курсив —
глифом, — а канала «центрирован» нет вовсе. ⇒ либо обойдись без стиля (замерь, даёт ли «абзац = голое римское
числительное» ровно 84 на ЧИСТОМ тексте и сколько даёт ложных), либо **приходи с доводом и ПИНОМ**, а не
правь обходчик молча. ⚠ Мой приор был внутренне противоречив; это моя ошибка, не твоя развилка.
### 4.2-бис ⛔ ПРАВИЛО ЗАГОЛОВКА УЖЕ ПРОТИВОРЕЧИТ САМО СЕБЕ — ряд **346** половина (б), и ты встанешь ровно на него
**Найдено оркестратором 18.09 чтением бэклога, в пак внесено намеренно.** Ты собираешься заставить грамматику
участвовать там, где режут. Но правило «что такое заголовок» **сегодня разъехалось по двум предикатам, и они
дают РАЗНЫЕ ответы на одном входе**. Строить поверх противоречия — значит закрепить его в третьем месте.
**Замер, уже снятый и пере-снятый на копии дерева (ряд 346, повторишь своим прибором):**
- Страж ДЛИНЫ живёт только в ингесте — `backend/internal/chunk/ingest.go:341`=`const chapterHeaderMaxRunes = 60`;
в чанкере длина не проверяется вовсе.
- Осмысленность НОМЕРА живёт только в чанкере — `backend/internal/chunk/chunker.go:315`=`v := section + num`.
- ⇒ на заголовке С СЕПАРАТОРОМ после юнита (`第四章:…`) длиной 6184 руны ингест говорит `false`, чанкер —
`ok=true`. ⚠ **Без сепаратора расхождения НЕТ** ни на одной длине, и без этой оговорки строка читается как
ложная: такую строку отвергают оба предиката.
- Отдельно: `第零章` (пролог нулевой главой) ингест режет главой, а чанкер заголовок НЕ снимает ⇒ `heading=""`,
и сырой CJK-маркер уезжает МОДЕЛИ.
**Половина (а) уже закрыта** актом `D39.225`: пин расхождения и паритет-тест стоят, оба под мутациями. Пин
написан так, что **краснеет ровно тогда, когда приземлится фикс** — это сделано намеренно, чтобы фиксер не смог
починить молча. ⇒ **ты — тот фиксер, и покрасневший пин здесь НЕ «протухший тест», а приглашение**; правишь его
вместе с правилом и объявляешь это в отчёте по норме «правка, вызванная заказанной сменой поведения».
**И вот зачем это в ЭТОМ паке, а не отдельным:** предмет один — «чем определяется, что строка есть заголовок
главы». Пока он живёт в двух предикатах, «подключить грамматику к EPUB» означает выбрать, к КОТОРОМУ из двух
подключаться, и любой выбор молча объявит второй неправым. ⇒ **сведи правило в ОДНО место и позови оттуда
всех троих** — ингест текстовый, ингест EPUB, чанкер. Это и есть «чистый архитектурный код» в формулировке
владельца, а не рефактор ради рефактора.
**Ряд 346 пере-проверь сама:** его прошлая редакция уже была однажды исправлена как ложная, и оговорка про
сепаратор — след той правки.
### 4.3 ЧТО ПРОИСХОДИТ, КОГДА ПОЛИТИКА НЕ ОБЪЯВЛЕНА: ПРОХОД С НАЗВАННОЙ АНОМАЛИЕЙ
**РОВНО ТАК, и довод взят из самой кодовой базы:** `backend/internal/pipeline/runner.go:389`=`merely costs money it did not mean to spend` — конфигурация,
которая верна и лишь стоит денег, которых не собиралась тратить, есть форма, о которой этот код
**ПРЕДУПРЕЖДАЕТ, а не отказывает**; отказы — для механизма, который не может работать вовсе. Кривое
оглавление — первое, а не второе.
⇒ книга без объявленной грамматики едет как сегодня в части (б), но человек **видит числом**
**Оговорка, без которой абзац лжёт:** правило (а) живёт в Go и действует на КАЖДЫЙ EPUB с осколками-тождествами,
объявлена грамматика или нет. Без грамматики отсутствует только (б).
⇒ человек видит числом, что получил: сколько глав,
сколько из них несут прозу, какова медиана и самая длинная, сколько фрагментов оглавления опознано
тождеством. Одной строкой в человеческой сводке и теми же числами в машинной.
**Почему не отказ:** большинство реальных EPUB собраны такими же конвертерами. Отказ на «оглавление
выглядит странно» сделал бы движок капризным к массовой форме входа и заставил бы объявлять грамматику для
каждой книги. Это не «приличная мультиязычность», а её противоположность.
### 4.4 ⛔ ЗАМЕР ПЕРЕД СТРОЙКОЙ — ПЕРВЫМ ДЕЛОМ, И БЕЗ ЕДИНОЙ СТРОКИ Go
Прежде чем править код, **реализуй правило грамматики на python и прогони по книге прогона**. Ответь числом:
сколько глав даёт «центрированный короткий абзац + римская цифра + строка капсом» против записей навигации.
**это и есть приёмочное число пака.** Если правило даёт число, не похожее на настоящую структуру книги, —
стройка начинается не с того, и лучше это узнать за ноль долларов и без правок дерева. Так же вчера был
пере-снят механизм группировки, и это сэкономило пак.
### 4.4-бис ⛔ ЧТО ЭТОТ ПАК СДВИНЕТ В ДЕНЬГАХ — сказано ЗАРАНЕЕ, потому что узнать это после лендинга дорого
**Правка правила ингеста двигает ключ манифеста, и не только у английской книги.** Конвенция записана в самом
коде: `backend/internal/pipeline/manifest.go:355`=`treats an ingest-rule change as a chunkerVersion bump`.
А в ключ сворачиваются оба`backend/internal/pipeline/manifest.go:281`=`Chunker: chunkerVersion` и
`backend/internal/pipeline/manifest.go:283`=`Structure: r.structure.Fingerprint()`, причём отпечаток несёт
версию алгоритма ДАЖЕ у пустой грамматики (`backend/internal/lang/structure.go:104`=`structureAlgoVersion + "-none"`).
**ключ манифеста КИТАЙСКОЙ книги сдвинется тоже.** Вдобавок §4.2-бис меняет ТЕКСТ чанка.
⛔ **И потому: акт `D39.269` п. B1 утверждал «китайская половина неподвижна» — это было верно про ПРЕДЫДУЩИЙ
пак и перестаёт быть верным про твой.** Не «обходи», а **НАЗОВИ**: в отчёте отдельной строкой — что сдвинулось,
у каких книг, и какой пере-снапшот это требует. Эррату к акту пишет оркестратор, не ты.
**И ещё одно следствие, за пределы твоей зоны:** пак холодного прогона
(`docs/COLDRUN_TWO_BOOKS_SESSION_PROMPT.md`) несёт ПРЕ-РЕГИСТРИРОВАННЫЕ числа среза английской книги и
стоп-правило «срез не совпал с пре-регистрированным → СТОП». После тебя крой другой, и те числа протухают.
**Твой пак лендится ДО фриза прогона**; пере-регистрацию делает пак прогона. Тебе — только строка в отчёте.
### 4.5 ЧТО В ЭТОТ ПАК НЕ ВХОДИТ — чтобы ты не расширяла его по своей инициативе
**Ряд 302** (несколько глав в ОДНОМ документе, разделённых якорями, схлопываются в одну) — **противоположный**
предмет, и дизайн вынес его из окна с названной ценой: единственное место, трогающее байтовый путь
извлечения, где ошибка ТИХАЯ. Не трогай.
**Ряд 201** (движковое «Глава N» доезжает читателю внутри текста) и **284** (название книги и заголовки
через термины) — соседние предметы того же дизайна; в этот пак не входят. Увидишь, что твоя правка их
задевает, — скажи, не чини.
**Большой перекрой** из дизайна (адресация главы контентом, четыре таблицы по ординалу) — **НЕ этот пак**.
Его механика в четырёх кругах не сошлась, и заходить в неё сейчас значит открыть предмет, который мы не
закроем до прогона.
---
## 5. Самопроверка исполнением — и где у ЭТОГО пака мягко
Ревью исполнением обязательно: своего кода, своих проб, своих результатов. **Субагенты разрешены.**
**Fable 5 — ОДИН советчик** с постоянным контекстом; он советчик, а не источник истины.
**ЧЕТЫРЕ МЕСТА, ГДЕ ЭТОТ ПАК МЯГОК ИМЕННО ПОТОМУ, ЧТО ОН ТАКОЙ:**
1. ⛔ **Твоя правка меняет КРОЙ, а крой versionируется.** По конвенции проекта правка правила нарезки
бампает версию нарезки, а та фолдится БЕЗУСЛОВНО — то есть у КАЖДОЙ книги, включая китайскую.
**Сегодня это бесплатно** (книг продуктового пути с оплаченными чанками нет), и именно поэтому пак идёт
ДО прогона, а не после. Предъяви цену явно, не умолчи её.
2. ⛔ **Тождество легко сделать почти-тождеством, и тогда оно станет порогом.** «Равно ярлыку» и «начинается
с ярлыка плюс немного» — разные правила: первое доказуемо, второе — эвристика с невидимой границей.
Замер: **60 тождеств и ОДНО почти-тождество** (титульная страница части). Если возьмёшь «почти», скажи,
где теперь граница и чем она обоснована.
3. ⛔ **Грамматика, которая режет ЭТУ книгу, легко окажется грамматикой этой книги.** Признак «римская
цифра в центрированном абзаце» обязан быть выведен из свойства ВЁРСТКИ, а не подобран под Кристоффа.
**Контрольный набор я назвал неверно, и неверно он звучал правдоподобно:** «китайская книга» — это TXT
(`books/gu-zhenren`), и EPUB-пути она не касается вовсе. **Настоящий контроль — два EPUB дерева:**
`books/isekai_majutsushi_jp.epub` (CJK-EPUB, получает умолчательную грамматику на EPUB-пути) и
`books/fifty_shades_1_en.epub` (латиница, цели навигации 1:1 с документами — отрицательный контроль:
правило (а) обязано на нём не сработать ни разу). Китайская книга — контроль только на сдвиг версии.
4. ⛔ **Пин на новую грамматику легко построить на той же фикстуре, из которой она выведена.** Тогда он
докажет, что правило работает на том, из чего сделано. Образец правильной формы в дереве есть — пин,
который режет книгу ИЗ ОДНОГО ФАЙЛА ДАННЫХ и падает без него.
---
## 6. Оси ревью
- **Код:** батарея + живые пробы + СВОИ адверсариальные посадки вне твоего списка. Пак, меняющий крой,
принимается прогоном через настоящий драйвер, а не чтением диффа.
- **Данные:** каждое значение грамматики — с доводом, из какого свойства вёрстки выведено.
- **Швы:** отпечаток структуры едет в крой; проверь, что он двигается ровно тогда, когда должен.
**Кодовому паку дополнительно:** пин на УСЛОВНОЕ сообщение обязан иметь фикстуру, где оно ОБЯЗАНО
прозвучать · перед отчётом сверь каждый клейм с результатом инструмента ЭТОЙ сессии · последний абзац
отчёта оказался планом — сделай сейчас · **править тесты ради зелени недопустимо**, но правка, вызванная
ЗАКАЗАННОЙ сменой поведения, есть обслуживание и ОБЪЯВЛЯЕТСЯ · дифф `^func Test` предъявляй исполнением.
---
## 7. Записка-план
До первой правки кода — записка: что делаешь, в каком порядке, что считаешь спорным. ⚠ Замер §4.4 идёт
ПЕРВЫМ и входит в записку числом, а не обещанием.
---
## 8. Заявление есть команда
Каждое число — с командой. У каждого НУЛЯ печатается контрольная величина.
**Ловушки среды:** обёртка `grep` чтит игнор-файлы и не видит `books/` — бери `command grep` · `find` здесь
**bfs**: относительное время только `-mmin`, и он тоже чтит игнор-файлы · командной утилиты для баз нет ·
**знаки и байты — разные популяции**, «текст файла» и «текст тела» — тоже: на этом я сегодня получил ноль
тождеств там, где их шестьдесят.
---
## 9. Эхо старта
До работы — ≤10 строк **СВОИМИ словами**: скоуп · инварианты · чего не делаешь. Шли первым действием.
---
## 10. Что НЕ удалось и где прибор слеп
Обязательная секция, две половины: «что не получилось» и **«что ты знаешь и не сказала»** — что невидимо ·
почему не чинила · чем закрывается.
---
## 11. Канал вопросов и право отказаться
Конфликт промта с деревом — **пинг, а не интерпретация**. Право сказать «этого делать не надо» с аргументом
есть и поощряется.
⚠ Этот промт написан после того, как я **сам ошибся в постановке дважды**: сперва обошёл предмет срезом,
потом склеил два факта в один. Оба раза поправил замер, а не рассуждение. Найдёшь третью — говори.
---
## 12. Прямой канал
`/tmp/textmachine-channel` — впиши свой блок первым делом. ⚠ Адрес из файла не доказывает, что сессия жива:
сверься с `ListAgents`.
---
## 13. Когда пак считается завершённым
- [ ] Замер §4.4 предъявлен ЧИСЛОМ до первой правки Go.
- [ ] У каждого пункта §4 — исход: сделано · не делаю с доводом · пинг.
- [ ] **Английская книга режется по своим настоящим главам**, и это предъявлено прогоном, а не рассуждением.
- [ ] **Китайская книга не изменилась** — предъявлено хешем.
- [ ] Ни одной ветки по языку или паре не добавлено — предъявлено грепом с контрольной величиной.
- [ ] Круги СОШЛИСЬ; прежние находки закрыты таблицей «находка → что сделано → **чем предъявлено**».
- [ ] Таблица мутаций полная, ТРИ исхода: поймано · выжило · **НЕ ИЗМЕРЕНО**; новые записи введены в гейт.
- [ ] Числа сняты ПОСЛЕ последней правки; гейт прогнан целиком; рядом с `ok` напечатан счёт скипов.
- [ ] Явное **«работа завершена, править не планирую»**.
**И последнее.** Предмет пака — **кто решает, где начинается глава**. Ответ, к которому надо прийти:
тождество решает движок, потому что это факт, а не догадка; АДРЕС границы решает грамматика книги; ИМЯ берётся
у того, кто его несёт. Всё, что сверх этого, решают данные книги. Если твоё
решение где-то ставит догадку движка на место данных — оно не готово, сколько бы тестов ни было зелёными.

File diff suppressed because one or more lines are too long

View file

@ -138,7 +138,7 @@
**Б7 — Шов и эмиттер 16, 17**
Находки: N042, N043, N044, N045, N046, N047, N048, N049, N115, N116, N117
**Приёмка:** N046 — форма якоря `mining.go:243-244`=`re-seed the bank with the auto rows`; N047 — атрибуция D39.159 п.7 и пере-суд «строки 139»; N048 — ОБЕ строки таблицы (97 и 103); N049 — хвост с `PD-427`; N044/N115 — StreamVersion **1.2** ⚠ (испр. 05.09: на HEAD она **1.3** — минор приехал лендингом `81a89e9`; критерий читать как «версия записана ВЕРНО», а не как литерал `1.2`); N045 — двенадцать якорей в токен-форме. (Прежний критерий про N032 снят: N032 живёт в Б6, испр. по замечанию контролёра.) §5§6 дока 16 сам себя баннирует как отработанный хендофф, внешних ссылок ноль — кандидат на вынос в `archive/reports/` с указателем (решение объявить, не делать молча).
**Приёмка:** N046 — форма якоря `backend/internal/pipeline/mining.go:246-246`=`re-seed the bank with the auto rows`; N047 — атрибуция D39.159 п.7 и пере-суд «строки 139»; N048 — ОБЕ строки таблицы (97 и 103); N049 — хвост с `PD-427`; N044/N115 — StreamVersion **1.2** ⚠ (испр. 05.09: на HEAD она **1.3** — минор приехал лендингом `81a89e9`; критерий читать как «версия записана ВЕРНО», а не как литерал `1.2`); N045 — двенадцать якорей в токен-форме. (Прежний критерий про N032 снят: N032 живёт в Б6, испр. по замечанию контролёра.) §5§6 дока 16 сам себя баннирует как отработанный хендофф, внешних ссылок ноль — кандидат на вынос в `archive/reports/` с указателем (решение объявить, не делать молча).
**Б8 — Движковые арх-доки 03, 09, 10, 12**

File diff suppressed because one or more lines are too long

View file

@ -1747,13 +1747,13 @@ langpacks — **0 файлов** при **16** прочитанных прибо
| читатель | адрес | что получает |
|---|---|---|
| `attachConsolidatedDst` → режим эмиссии | `backend/internal/pipeline/terminologist.go:1415`=`Terms the role did not answer are left untouched` | передачу ⇒ `draft` вместо отката к догадке куска |
| `attachClassifiedType` / `attachClassifiedGender` | `backend/internal/pipeline/terminologist.go:1430`=`attachClassifiedGender stamps` | тип и род из базиса |
| `consolidatedRows``ConsolidationKeyConflicts` | `backend/internal/pipeline/terminologist.go:747`=`membank.ConsolidationKeyConflicts(consolidatedRows(cands, out), bank)` | судится, см. решение ниже |
| `CanonConflicts` | `backend/internal/pipeline/terminologist.go:725`=`terminology.CanonConflicts(cands, out, canon)` | судится, см. решение ниже |
| `attachConsolidatedDst` → режим эмиссии | `backend/internal/pipeline/terminologist.go:1418`=`Terms the role did not answer are left untouched` | передачу ⇒ `draft` вместо отката к догадке куска |
| `attachClassifiedType` / `attachClassifiedGender` | `backend/internal/pipeline/terminologist.go:1433`=`attachClassifiedGender stamps` | тип и род из базиса |
| `consolidatedRows``ConsolidationKeyConflicts` | `backend/internal/pipeline/terminologist.go:750`=`membank.ConsolidationKeyConflicts(consolidatedRows(cands, out), bank)` | судится, см. решение ниже |
| `CanonConflicts` | `backend/internal/pipeline/terminologist.go:728`=`terminology.CanonConflicts(cands, out, canon)` | судится, см. решение ниже |
| `ConsolidationConflicts` (§G2, свои против своих) | рядом, тот же цикл | судится |
| `labelRows``TypeLabelMismatches` | там же | судится |
| счётчики `Consolidated`/`Declined`/`Unanswered` | `backend/internal/pipeline/terminologist.go:719`=`res.Consolidated++` | ⛔ НЕ вливать базисные в общий набор до цикла счёта: поле сменит смысл без единой строки об этом |
| счётчики `Consolidated`/`Declined`/`Unanswered` | `backend/internal/pipeline/terminologist.go:722`=`res.Consolidated++` | ⛔ НЕ вливать базисные в общий набор до цикла счёта: поле сменит смысл без единой строки об этом |
| таблица стопа и её сайдкар | ⚠ ИСТОРИЧЕСКОЕ (до этого пака формула была `row.Invented = dst != "" && !proposedByDrafts(dst, c.Variants)`, и адрес намеренно НЕ в якорной форме: цель не уехала, а ИЗМЕНИЛАСЬ — её починил этот пак, и подставить найденный в цели токен значило бы подогнать якорь под то, что там оказалось). Живая строка — `backend/internal/pipeline/mining.go:525`=`row.Invented = dst != "" && !row.SettledByBasis` | отдельное состояние, см. ниже |
| очередь спорных | `backend/internal/pipeline/stopcontest.go:83`=`if r.SettledByBank` | ⛔ решается ОТДЕЛЬНО, не наследуется по совпадению имени |
| проекция банка наружу | `backend/internal/pipeline/bankexport.go:128`=`NeverAsked: t.BankSettled` и `backend/internal/pipeline/bankexport.go:269`=`Contradicts: row.Contradicts, BankHolds: row.BankHolds` | АДДИТИВНОЕ поле, см. §5 |
@ -1797,7 +1797,7 @@ langpacks — **0 файлов** при **16** прочитанных прибо
##### 6. Юниты (§4.5) — ОТДЕЛЬНЫЙ ПРОХОД, батчер не трогаю
Семьи и серии сегодня детектируются НАД `paid`, то есть ПОСЛЕ фильтра
(`backend/internal/pipeline/terminologist.go:574`=`terminology.DetectFamilies(paid, fp)`), и комментарий выше объясняет причину: детектировать над полным
(`backend/internal/pipeline/terminologist.go:577`=`terminology.DetectFamilies(paid, fp)`), и комментарий выше объясняет причину: детектировать над полным
набором значит рассогласовать юниты с батчами, из них построенными. **Но это довод про БАТЧИ, а не про
предикат.** Если считать юнит над `paid`, юнит будет определён тем самым фильтром, который он питает, —
круг. ⇒ юнит для предиката считается **отдельным чистым проходом над ВСЕМИ кандидатами**, батчер остаётся
@ -1828,7 +1828,7 @@ langpacks — **0 файлов** при **16** прочитанных прибо
- **Ряд 490** (страж разрыва окон глав) — $0, беру, образец `backend/internal/membank/memvoice.go:271`=`func VoiceWindowGaps`;
у образца беру и ГАРАНТИЮ: намеренный разрыв законен ⇒ исход громкая строка, а не отказ загрузки.
- **Ряд 448** — пере-формулировка обязана обновить И голден каталога операторских сообщений
(`backend/internal/pipeline/testdata/operator-messages.txt:141`=`terminology: this book had already paid`), и это
(`backend/internal/pipeline/testdata/operator-messages.txt:143`=`terminology: this book had already paid`), и это
обслуживание заказанной смены поведения (`D39.183`), а не подгонка под зелень. Объявляю заранее.
- **Ряд 487** пак НЕ чинит, но посылку «решено ⇒ `draft`» пиню: без пина завтрашняя починка скачком двинет
набор решённых, то есть деньги.
@ -2411,7 +2411,7 @@ decision»).
| пункт | исход | чем предъявлено |
|---|---|---|
| §4.1 громкий отказ, когда у ЦЕЛИ нет строк инъекции | сделано, и ШИРЕ заказа: арм пять, а не три | `backend/internal/pipeline/injectionpreflight.go:96`=`func (r *Runner) checkInjectionTexts` + вызов `backend/internal/pipeline/runner.go:389`=`if err := r.checkInjectionTexts(logger); err != nil {` рядом с соседним отказом `:364`=`if forWrite && r.Pipeline.Gates.Terminology.Enabled && r.pack == nil {`; пин `TestABankItsTargetCannotStateRefusesBeforeItIsBought` (6 подтестов) |
| §4.1 громкий отказ, когда у ЦЕЛИ нет строк инъекции | сделано, и ШИРЕ заказа: арм пять, а не три | `backend/internal/pipeline/injectionpreflight.go:96`=`func (r *Runner) checkInjectionTexts` + вызов `backend/internal/pipeline/runner.go:401`=`if err := r.checkInjectionTexts(logger); err != nil {` рядом с соседним отказом `:364`=`if forWrite && r.Pipeline.Gates.Terminology.Enabled && r.pack == nil {`; пин `TestABankItsTargetCannotStateRefusesBeforeItIsBought` (6 подтестов) |
| §4.1 предикат ПОЛНОТЫ, а не заголовка | сделано | `lang.InjectionTexts.MissingKeys` / `Authored` / `GenderKeyGaps` + реестр `roleInjectionKeys` (`chunkrun.go`); пины `TestTheInjectionKeysAreOneVocabulary`, `TestAPartialInjectionSetIsVisibleToTheGuard` |
| §4.2 отказ роли перестаёт быть передачей (путь **а**) | сделано; сентинел НЕ тронут | `terminology.go` — ветвь `declined` рядом с `isNoDst`; пин `TestAProseDeclineLandsInTheDeclineBucketAndNotInTheBank` |
| §4.2 мусор разведён с отказом по корзинам | сделано | ветвь `hasLetter` + счётчики `NoLetters`/`DeclinedByPhrase`; пины `TestARenderingWithNoLettersReadsAsUnansweredNotAsCanon` (парсер) и `TestADeclineAndGarbageLandInDifferentBuckets` (сквозной, все три исхода в одном ответе) |
@ -2431,7 +2431,7 @@ decision»).
2. `gates.banknote` — черновик спрашивается о таблице терминов;
3. `book.yaml glossary_seed` — сид;
4. **`<book_id>.mined-delta.yaml`** — подписанные владельцем строки. ⚠ Путь ДЕРИВИРУЕТСЯ (`backend/internal/config/book.go:210`=`b.MinedDelta = filepath.Join(dir, b.BookID+MinedDeltaSuffix)`), поэтому `book.MinedDelta != ""` истинно ВСЕГДА и как арм негоден — проверяется НАЛИЧИЕ файла (`decisionFilePresent`);
5. **авто-банк, оставленный в сторе предыдущим прогоном** (`backend/internal/pipeline/mining.go:723`=`func (r *Runner) loadAutoBank`). Гейт — ран-локальный конфиг книги (`D39.126` п.2а), а строки в сторе — нет: книга с выключенными гейтами, без сида и с банком от прошлого прогона инжектит так же молча.
5. **авто-банк, оставленный в сторе предыдущим прогоном** (`backend/internal/pipeline/mining.go:754`=`func (r *Runner) loadAutoBank`). Гейт — ран-локальный конфиг книги (`D39.126` п.2а), а строки в сторе — нет: книга с выключенными гейтами, без сида и с банком от прошлого прогона инжектит так же молча.
Армы спрашиваются в порядке цены (`backend/internal/pipeline/injectionpreflight.go:40`=`func (r *Runner) bankRowsReachTheWire`): два поля конфига → один `stat` → чтение стора, и стор трогается только когда всё дешёвое сказало «нет». **Ошибка чтения дверью не считается** — WARN и «строк нет», по норме «предупреждение о деньгах не выдумывается из ошибки».

View file

@ -89,7 +89,7 @@ excision_suspect / ok) 1:1, не изобретать заново. Метрик
`configs/models.yaml` (модели/цены/таймауты, с датой проверки). В конфиг ядра входят: состав/порядок стадий,
роль→модель, версии промптов, пороги гейтов, **режим инъекции глоссария (selective | full_prefix)** (Р5
требует обе схемы), токен-бюджеты сборки контекста (инъекция, STM, overlap), лимит регенераций до эскалации,
**именованные эскалационные цепочки** (список моделей; ⚠ **испр. 10.09:** какую цепочку берёт прогон, решает ЗАГРУЗКА по `content_policy`, а не раннер — `backend/internal/config/pipeline.go:529`=`LOAD, not by the runner` (⚠ испр. 17.09: было :502); раннер лишь исполняет уже разрешённый хоп, `backend/internal/pipeline/escalation.go:125`=`st.ResolvedHop`. Слово «channel» отставлено — `backend/internal/config/pipeline.go:266`=`LegacyChannel is the RETIRED`), fan-out N для
**именованные эскалационные цепочки** (список моделей; ⚠ **испр. 10.09:** какую цепочку берёт прогон, решает ЗАГРУЗКА по `content_policy`, а не раннер — `backend/internal/config/pipeline.go:597`=`LOAD, not by the runner` (⚠ испр. 17.09: было :502); раннер лишь исполняет уже разрешённый хоп, `backend/internal/pipeline/escalation.go:125`=`st.ResolvedHop`. Слово «channel» отставлено — `backend/internal/config/pipeline.go:332`=`LegacyChannel is the RETIRED`), fan-out N для
C2, cache TTL per-стадия. В models.yaml: цены (+cache write/read), профиль таймаутов/ретраев per-модель,
`extra_body`. Зашито в раннер: циклы по главам/чанкам, ветвление по гейтам, механика эскалации/ретраев,
семантика «эскалация → re-gate → флаг» (диаграмма pipeline.puml перегейтовку не рисует — реализую re-gate
@ -103,7 +103,7 @@ C2, cache TTL per-стадия. В models.yaml: цены (+cache write/read), п
## 5. [НУЖНО РЕШЕНИЕ] — СНЯТО, всё развязано
> ⚠ **§5 СНЯТ — а код на него ещё ссылается, поэтому вот куда он уехал** (заглушка, испр. 10.09, по образцу заглушки §2 выше). Три не-тестовых сайта продолжают адресовать эту секцию: `backend/internal/config/pipeline.go:498`=`a defect flips the chunk to flagged` (⚠ испр. 17.09: было :471) (формула премиум-бюджета — см. `01-decisions.md`, там же сказано, что в коде её НЕТ и почему), `backend/internal/llm/llm.go:106`=`JSONOnly bool` (провайдерский стриминг — отнесён к Ф2, §3 этого дока), `backend/internal/pipeline/render.go:34`=`explicit re-translation, not a silent cache miss` (§3.4 того же дока). ⇒ читающий из кода приходит сюда и находит указатель, а не пустоту.
> ⚠ **§5 СНЯТ — а код на него ещё ссылается, поэтому вот куда он уехал** (заглушка, испр. 10.09, по образцу заглушки §2 выше). Три не-тестовых сайта продолжают адресовать эту секцию: `backend/internal/config/pipeline.go:566`=`a defect flips the chunk to flagged` (⚠ испр. 17.09: было :471) (формула премиум-бюджета — см. `01-decisions.md`, там же сказано, что в коде её НЕТ и почему), `backend/internal/llm/llm.go:106`=`JSONOnly bool` (провайдерский стриминг — отнесён к Ф2, §3 этого дока), `backend/internal/pipeline/render.go:34`=`explicit re-translation, not a silent cache miss` (§3.4 того же дока). ⇒ читающий из кода приходит сюда и находит указатель, а не пустоту.
Пять вопросов 04.07 закрыты, карта развязок — в баннере вверху (тела D11/D14/D19/D23 — в слайсе `../archive/architecture/05-decisions-D1-D38.md`); пятый, не попавший в карту, — пункты оркестратору (фазовые пометки диаграмм, владелец задачи cache-hit), отработан арх-ресетом D39. Формулировки вопросов — `../archive/PROGRESS-2026-07-04-10.md`.

File diff suppressed because one or more lines are too long

View file

@ -129,7 +129,7 @@ Go-гейт стоит на v1 без поглощения кавычек: `back
### [CONFIRMED_LOST] (4) Golden-счётчики голоса (дописка 41): в capture.golden voice-счётчиков нет
Подтверждено отсутствие: `grep -c -i voice backend/internal/pipeline/testdata/golden/capture.golden` = 0 (ни одного вхождения); в testdata/golden/pipeline.yaml ключа `voice:` нет (grep пуст) — гейт opt-in OFF по умолчанию (`backend/internal/config/pipeline.go:315`=`type VoiceGate struct` — ⚠ испр. 16.09: было :279-281), фикстура его не включает, значит golden не пинит ни n_voice_flags/voice_detail, ни VoiceFlags/SpoilerLeaks (quality.go:269-278, 436 — счётчики существуют в коде, но в golden-контур не заведены). Хвост D39.58 реален и жив.
Подтверждено отсутствие: `grep -c -i voice backend/internal/pipeline/testdata/golden/capture.golden` = 0 (ни одного вхождения); в testdata/golden/pipeline.yaml ключа `voice:` нет (grep пуст) — гейт opt-in OFF по умолчанию (`backend/internal/config/pipeline.go:381`=`type VoiceGate struct` — ⚠ испр. 16.09: было :279-281), фикстура его не включает, значит golden не пинит ни n_voice_flags/voice_detail, ни VoiceFlags/SpoilerLeaks (quality.go:269-278, 436 — счётчики существуют в коде, но в golden-контур не заведены). Хвост D39.58 реален и жив.
### [CONFIRMED_LOST] (5) Слот инъекции голоса (дописка 24): инъекционная половина построена условно и НЕ включена, оба провода
@ -174,7 +174,7 @@ Go-гейт стоит на v1 без поглощения кавычек: `back
### Б-106 — тело строки 106 на 09.08.2026
**Слой 3 НЕ достроен: контракт редактора = full-regen, а целевой — узкие мандаты + ДИФФЫ.** `docs/architecture/09-target-architecture.md:111`=`узкие мандаты + диффы` дословно: «research/07 предписывает узкие мандаты + диффы, а не полную перегенерацию — сейчас ровно наоборот (4 мандата, full-regen), что и породило хвост инверсий» (⚠ прежний голый якорь `:121-126` съехал на слой 5/6); целевой контракт — редактор правит anchored search/replace-спанами с детерминированным Go-apply (ограниченный blast-radius, дешевле, аудируемо, структурно omission-safe). Пак-16 построил петлю РЕМОНТА (починка флагнутого спана постфактум), а не diff-контракт роли ⇒ это разные вещи. **Строки не было ни одной** — дыра учёта, найденная сверкой 02.08 (грепы `diff`/`мандат`/`span` по бэклогу = 0). Смежное: арм Q4b «diff-fidelity+style редактор» (`exp15:108`) не прогонялся НИ РАЗУ, хотя трек B объявлен завершённым. **ЭМПИРИКА ПОЛУЧЕНА (D39.108, экспы 19/20):** дифф-контракт механически исправен (комплаенс 0.966 на боевой единице, аппликатор 15/15), но по замороженному гейту Q4b диффы ОТЛОЖЕНЫ (fidelity-trap 11/12; дороже full-regen на dspro×ru ×2.52.9 при невскрытой причине; D21.4/D21.10 закрыты явно). **Эксп-21 (D39.117):** полный regen выигрывает у точечного ремонта (`C2/A_law` 3.75 Holm 0.0006) — довод против диффов, НЕ опровержение: чинил дешёвый фиксер по предложению, а не сильный редактор узким мандатом; гейт «перепроверить до стройки» в силе, приоритет понижен
**Слой 3 НЕ достроен: контракт редактора = full-regen, а целевой — узкие мандаты + ДИФФЫ.** `docs/architecture/09-target-architecture.md:115`=`узкие мандаты + диффы` дословно: «research/07 предписывает узкие мандаты + диффы, а не полную перегенерацию — сейчас ровно наоборот (4 мандата, full-regen), что и породило хвост инверсий» (⚠ прежний голый якорь `:121-126` съехал на слой 5/6); целевой контракт — редактор правит anchored search/replace-спанами с детерминированным Go-apply (ограниченный blast-radius, дешевле, аудируемо, структурно omission-safe). Пак-16 построил петлю РЕМОНТА (починка флагнутого спана постфактум), а не diff-контракт роли ⇒ это разные вещи. **Строки не было ни одной** — дыра учёта, найденная сверкой 02.08 (грепы `diff`/`мандат`/`span` по бэклогу = 0). Смежное: арм Q4b «diff-fidelity+style редактор» (`exp15:108`) не прогонялся НИ РАЗУ, хотя трек B объявлен завершённым. **ЭМПИРИКА ПОЛУЧЕНА (D39.108, экспы 19/20):** дифф-контракт механически исправен (комплаенс 0.966 на боевой единице, аппликатор 15/15), но по замороженному гейту Q4b диффы ОТЛОЖЕНЫ (fidelity-trap 11/12; дороже full-regen на dspro×ru ×2.52.9 при невскрытой причине; D21.4/D21.10 закрыты явно). **Эксп-21 (D39.117):** полный regen выигрывает у точечного ремонта (`C2/A_law` 3.75 Holm 0.0006) — довод против диффов, НЕ опровержение: чинил дешёвый фиксер по предложению, а не сильный редактор узким мандатом; гейт «перепроверить до стройки» в силе, приоритет понижен
### Б-108 — тело строки 108 на 09.08.2026

View file

@ -180,7 +180,7 @@ cmp-сверка обязательна (D39.138 п.3).
(`platform/internal/books/books.go:118-121`=`books: the intake form is not usable`) и равенство `source == target` не проверяет никто, а
пар-промпты в репозитории есть ТОЛЬКО для `zh-ru`; отсутствие промпта пары — жёсткая ошибка
конфигурации, не деградация («no prompt for pair %q role %q … never silently substitute another
pair's conventions», `backend/internal/config/pipeline.go:1065`=`no prompt for pair` — ⚠ испр. 17.09: было :1038, ранее :952-956). До 0.3.0 книга в
pair's conventions», `backend/internal/config/pipeline.go:1169`=`no prompt for pair` — ⚠ испр. 17.09: было :1038, ранее :952-956). До 0.3.0 книга в
неподдерживаемой паре принималась, ложилась на диск, разбиралась, проводила пользователя через
денежный экран — и умирала на старте прогона без причины. Отсюда `Capabilities.language_pairs[]`
со статусом и код отказа на интейке (`errors[].code: unsupported_pair`).
@ -305,7 +305,7 @@ SQL и до 0.3.0 не отдавалась: `b.chapter_count - (select count(*)
Флагнутый юнит легально приходит С ТЕКСТОМ в двух случаях: косметическая зачистка санитайзера («the
chunk is NOT lost — the cleaned text is committed as the export», `backend/internal/pipeline/disposition.go:160`=`the cleaned text is committed as the export` — ⚠ испр. 16.09: было :96-104) и
c-lite member-drop (`pipeline/export.go:203-207`=`if exp.GhostRows > 0`). Поэтому состояние выводится из ПАРЫ (вердикт +
c-lite member-drop (`backend/internal/pipeline/export.go:203-207`=`if exp.GhostRows > 0`). Поэтому состояние выводится из ПАРЫ (вердикт +
наличие финального текста): флаг+текст → `translated` с замечанием; флаг+пусто → `withheld`.
Причина флага при этом ПРОИЗВОЛЬНА («единственный легальный случай» снято ревью round-2): при c-lite
@ -356,7 +356,7 @@ drop юнит несёт причину ПЕРВОГО выпавшего чле
**Фантом `auto` в провенансе убран ещё в 0.2.0:** комментарий схемы движка (`migrate.go:193`:
`seed|ruby|auto`) устарел — `"auto"` пишет СТАТУС, не провенанс (`backend/internal/membank/memseed.go:424`=`Status: "auto", Source: "ruby"` — ⚠ испр. 16.09: было :328:
`Status:"auto", Source:"ruby"`), майнинг ставит `mined` (`backend/internal/pipeline/mining.go:763`=`e.Source = "mined"` — ⚠ испр. 16.09: было :424).
`Status:"auto", Source:"ruby"`), майнинг ставит `mined` (`backend/internal/pipeline/mining.go:794`=`e.Source = "mined"` — ⚠ испр. 16.09: было :424).
**Ложный друг `source`.** У движка колонка `source` — это ПРОВЕНАНС. Поэтому в контракте провенанс
зовётся `origin`, формы термина — `src`/`dst`, а имя `source` в схеме банка не используется вовсе.
@ -372,7 +372,7 @@ drop юнит несёт причину ПЕРВОГО выпавшего чле
### 2.9. Подпись — набор решений — ✓ выведено
Конвейер заменяет банк целиком (`store/migrate.go:169-170`=`aggregation/classification live in the pipeline`), поэтому `PATCH /term/{id}` молча не
Конвейер заменяет банк целиком (`backend/internal/store/migrate.go:169-170`=`aggregation/classification live in the pipeline`), поэтому `PATCH /term/{id}` молча не
работает. Механика дословно: «for EACH term either promote it into the mined-delta file OR decline it
in the mined-rejects file, then resume» (`backend/internal/pipeline/mining.go:58`=`func (r *Runner) runBankMiningStop` — ⚠ испр. 16.09: было :81,243; ⚠ фраза «the stop clears once
every proposed term is promoted or rejected» ОПРОКИНУТА D39.144 — движок авто-продолжает с
@ -1505,8 +1505,8 @@ focus»), то есть цена платится на горячем пути
### G. Draft-only конвейер (PD-202) — ОТВЕЧАЮ: ДА, начерновленная глава «сделана»; правлю канон
Проверено чтением обеих зон, и цена выше заявленной. У движка деплой без второго прохода — не
экзотика: разбиение выводится из РОЛЕЙ стадий (`pipeline/waverun.go:43-53`=`non-translator (editor/other) stage`), конфиг требует лишь
«хотя бы одна стадия» (`backend/internal/config/pipeline.go:953`=`at least one stage is required` — ⚠ испр. 17.09: было :926, ранее :841), ветка живая и покрыта тестом
экзотика: разбиение выводится из РОЛЕЙ стадий (`backend/internal/pipeline/waverun.go:43-53`=`non-translator (editor/other) stage`), конфиг требует лишь
«хотя бы одна стадия» (`backend/internal/config/pipeline.go:1035`=`at least one stage is required` — ⚠ испр. 17.09: было :926, ранее :841), ветка живая и покрыта тестом
(`contractblockers_test.go:115-136`); в репозитории такого конфига пока нет, но выбирает его
оператор, и платформа шаблон не читает. У платформы `units_done` — жёстко второй проход
(`pgstore/sink.go:252-263`, `readmodel.go:200-208`).

View file

@ -23,7 +23,7 @@
- **Потолки — wiring, не семантика:** `Ceilings` намеренно исключены из `BriefHash` («Wiring fields (paths, ceilings, db) deliberately excluded», `backend/internal/config/book.go:345`=`Wiring fields (paths, ceilings, db) deliberately excluded`; канон — `backend/internal/config/book.go:369`=`canon := struct {`) ⇒ смена ДЕНЕЖНОГО потолка не двигает ни снапшот, ни ре-билл (D39.110 п.2б). ⚠ Верно ровно про ДЕНЬГИ: потолок СЕГМЕНТАЦИИ `edit_ceiling_out` в снапшот ВХОДИТ и меняет границы чанков (`backend/internal/pipeline/snapshot.go:34`=`EditCeilingOut int`).
- **`status --json` отдаёт фигуры платформе:** `committed_usd`, `reserved_usd`, `book_ceiling_usd`, `ceiling_pct` = 100·(committed+reserved)/book_ceiling (`backend/internal/pipeline/status.go:342`=`book_ceiling_usd,omitempty`; арифметика `ceiling_pct``backend/internal/pipeline/status.go:949`=`100 * (committed + reserved)`). Дневной фигуры в status нет (PD-157 — `platform/docs/DEFECT_REGISTER.md`, греп `PD-157`).
- **Леджер = НИЖНЯЯ граница; строка 78 снята с трекера 10.09, но её ЖИВОЙ ОСТАТОК несёт строка 377.****Испр. 10.09 — прежняя редакция этого пункта, написанная в тот же день, была НЕВЕРНА, и ошибка моя.** Она утверждала, будто оплаченный `2xx` с нечитаемым телом «прежде в `request_log` не попадал» и что это починил пак оборванных вызовов. Замер: ветвь `BilledDecodeError` писала свою строку с пометкой оценки ещё до пака — на `3f05fab^` это `stagerun.go:629`, введено коммитом `2f91b04` **24.07**, а пак этой ветви не касался вовсе (хитов `Estimated` в его диффе по `stagerun.go`**0**). И носитель я процитировал ЧУЖОЙ: `backend/internal/pipeline/cutcall.go:133`=`rl := r.baseRequestLog(c.stage, c.chunk` обслуживает `*llm.AttemptCutError` (`backend/internal/pipeline/stagerun.go:745`=`var cut *llm.AttemptCutError` — ⚠ испр. 17.09: было :802, ранее :741), а речь шла о `*llm.BilledDecodeError` (`stagerun.go:709`) — это разные классы, и второй до `cutcall.go` не доходит. **Что пак действительно купил:** строку `request_log` за вызов, который оборвали МЫ (прежде он падал в ветвь «ничего не куплено» и получал $0-строку) — вот это `cutcall.go:133`. **Что остаётся правдой:** леджер — нижняя граница, потому что обрыв, за которым ретрай УСПЕЛ, исчезает бесследно (строка **377**). Живой замер `D39.86` — 3 вызова из 14, неизвестность $0.015111 при леджере $0.114378.
- **ГРАНИЦА ДЕНЕГ ЗА ОБОРВАННЫЙ НАМИ ВЫЗОВ — ФАКТ ДОСТАВКИ, а не статус-строка** (пак строки 360, акт `D39.232`; слово владельца — `D39.230`). Деньги берутся, только когда провайдер подтвердил запрос ответным объектом: `backend/internal/llm/attemptcut.go:221`=`Billable: answered && tr.afterHeaders()`. Наш собственный дедлайн даёт свой класс `attempt_timeout` без ретрая; дедлайн вычисляется ИЗ БЮДЖЕТА вызова, а не назначается (`backend/internal/llm/attemptcut.go:269`=`func (p RetryProfile) deriveDeadline`), так что эскалация, удвоившая `max_tokens`, удваивает и время. Оплаченный обрыв получает чекпойнт, и резюм его НЕ перепокупает. ⛔ Пометка «это оценка» — УСЛОВИЕ слова владельца (`D39.230` п.1) — сегодня в движке живёт ТРЕМЯ носителями (колонка `Estimated` в `request_log``backend/internal/store/requestlog.go:41`=`Estimated marks a row whose CostUSD`; пара `estimated_rows`/`estimated_usd` в `status --json``backend/internal/pipeline/status.go:911`=`Re-payment projection (spec`; печать оператору — `backend/cmd/tmctl/render.go:402`=`the degraded/err tail`), но НАРУЖУ не выходит ни одним каналом шва: строка **382**. ⚠ Формулировка испр. 10.09 — прежняя («живёт только в `status --json`») звала следующего чинить пометку, которая уже есть.
- **ГРАНИЦА ДЕНЕГ ЗА ОБОРВАННЫЙ НАМИ ВЫЗОВ — ФАКТ ДОСТАВКИ, а не статус-строка** (пак строки 360, акт `D39.232`; слово владельца — `D39.230`). Деньги берутся, только когда провайдер подтвердил запрос ответным объектом: `backend/internal/llm/attemptcut.go:221`=`Billable: answered && tr.afterHeaders()`. Наш собственный дедлайн даёт свой класс `attempt_timeout` без ретрая; дедлайн вычисляется ИЗ БЮДЖЕТА вызова, а не назначается (`backend/internal/llm/attemptcut.go:269`=`func (p RetryProfile) deriveDeadline`), так что эскалация, удвоившая `max_tokens`, удваивает и время. Оплаченный обрыв получает чекпойнт, и резюм его НЕ перепокупает. ⛔ Пометка «это оценка» — УСЛОВИЕ слова владельца (`D39.230` п.1) — сегодня в движке живёт ТРЕМЯ носителями (колонка `Estimated` в `request_log``backend/internal/store/requestlog.go:41`=`Estimated marks a row whose CostUSD`; пара `estimated_rows`/`estimated_usd` в `status --json``backend/internal/pipeline/status.go:911`=`Re-payment projection (spec`; печать оператору — `backend/cmd/tmctl/render.go:432`=`the degraded/err tail`), но НАРУЖУ не выходит ни одним каналом шва: строка **382**. ⚠ Формулировка испр. 10.09 — прежняя («живёт только в `status --json`») звала следующего чинить пометку, которая уже есть.
- **Согласие на пере-оплату — ДВИЖКОВЫЙ гейт**. Прогон, который пере-покупает уже оплаченные юниты дороже порога, ОСТАНАВЛИВАЕТСЯ и требует явного согласия. Порог по умолчанию ратифицирован D20.2-Q2: `min($0.50, 5% × ProjectedBookUSD)`. Носитель — `backend/internal/pipeline/rebill.go`, греп `rebillConsentThreshold`; единственная ручка книги — `rebill_consent_usd` (валидатор `backend/internal/config/book.go`, греп `rebill_consent_usd`; 0 = ратифицированный дефолт). ⚠ Порог считается БЕЗ контура банк-ролей (D39.182 §3), то есть срабатывает позже, чем подсказывает полная проекция.

View file

@ -44,7 +44,7 @@
*Прецеденты:* потолок аргументом (D39.110 п.2б`--ceiling-usd`, D39.122) · `--verify-bank`
флагом (`platform/internal/runner/engine.go:116`=`func TranslateArgs(workdir string, verifyBank bool`) · дефолт `project_db` по конвенции каталога
(`backend/internal/config/book.go:200`=`b.ProjectDB = filepath.Join(dir, b.BookID+".db")`) · конвенционное опциональное чтение `.auto-bank.yaml`
(`backend/internal/pipeline/mining.go:703`=`.auto-bank.yaml`).
(`backend/internal/pipeline/mining.go:734`=`.auto-bank.yaml`).
2. **Посреди прогона внутрь идёт ТОЛЬКО сигнал.** `SIGTERM` = «дочти чанк и отпусти лок» — ⚠ **это ратифицированная ЦЕЛЬ, а не сегодняшнее дерево (пометка 10.09).** Владелец ратифицировал две остановки (`D39.234` п.1а): первый сигнал — мягкий, он и означает «дочти и отпусти». В КОДЕ его ещё нет: `backend/cmd/tmctl/main.go:208`=`signal.NotifyContext(context.Background()` — ⚠ испр. 16.09: было :207 (пустая строка) отменяет контекст прогона, летящий вызов рвётся, книжится по оценке и метится `cancelled`. ⇒ норму НЕ переписывать под дерево, дерево догоняет норму; носитель работы — строка бэклога **381**, где этот адрес назван третьим. Всё
остальное — на границах процессов. Арбитр — эксклюзивный flock проекта на всю жизнь прогона
@ -66,7 +66,7 @@
4. **Незнакомый флаг = громкий отказ при спавне.** Сегодня это так по устройству разбора аргументов
(`backend/cmd/tmctl/invocation.go:128`=`flag.NewFlagSet(cmd, flag.ContinueOnError)`); зафиксировать законом, а не полагаться на случайность.
5. **Глагол зовётся ТЕМ ЖЕ пиннутым бинарём, что и возобновляемая попытка** (`runs/spawn.go:250-258`=`engineBinary is the path an attempt is PINNED to`,
5. **Глагол зовётся ТЕМ ЖЕ пиннутым бинарём, что и возобновляемая попытка** (`platform/internal/runs/spawn.go:250-258`=`engineBinary is the path an attempt is PINNED to`,
строка бэклога 139). Иначе файл, записанный новым бинарём, читается старым — а при нестрогом
загрузчике сида (строка **212**) новое поле теряется МОЛЧА (⚠ **адрес испр. 10.09: строки 212 в живом трекере НЕТ** — она закрыта; факт оставлен как довод, номер снят, чтобы не звать читателя в пустоту).

File diff suppressed because one or more lines are too long

View file

@ -5271,9 +5271,9 @@ user-сообщения» на глаз. 2 436 токенов на 7 525 зна
**ДЕФЕКТ ДВИЖКА УЖЕ ЗАКРЫТ, И Я ЭТО ПРОВЕРИЛ КОДОМ, А НЕ ПРЕДПОЛОЖИЛ.** Лендинг `7d0c6f2`
(28.08) ввёл ось `Capability.SystemMessages`: у провайдера `gemini` стоит
`backend/configs/models.yaml:137`=`system_messages: single`, адаптер склеивает ВЕДУЩИЙ system-ран в
`backend/configs/models.yaml:183`=`system_messages: single`, адаптер склеивает ВЕДУЩИЙ system-ран в
одно сообщение и громко падает на системном ходе после не-системного
(`backend/internal/llm/httpllm.go:528`=`func toOpenAIMessages`), инвариант запинен тестом
(`backend/internal/llm/httpllm.go:766`=`func toOpenAIMessages`), инвариант запинен тестом
(`backend/internal/llm/systemmessages_test.go:78`=`TestSystemMessagesSingleCarriesTheInjection`).
⚠ Первая редакция ЭТОГО ЖЕ абзаца, написанная час назад, объявляла дефект живым и слала пинг в
бэкенд — она стояла на строке `00-provider-quirks.md:158`, устаревшей на один день.
@ -7786,7 +7786,7 @@ $0.040093 — расхождение 0.0%). Значит прайс-таблиц
**Что остаётся верным после правки:** редакторская стадия покупает **беглость и термин**; ось
верности она не двигает; и **прибора этой оси у нас по-прежнему нет** — панель объявлена слепой к
ней (`23-editor-tier.md:6056`=`оси ВЕРНОСТИ владельца`). Утверждение «мы измеряем половину цели»
ней (`docs/experiments/23-editor-tier.md:6057`=`оси ВЕРНОСТИ владельца`). Утверждение «мы измеряем половину цели»
устояло; утверждение «за вторую половину мы платим» — снято.
**Класс моей ошибки:** «не покупает» прочитано как «платит» — усиление вывода в сторону тревоги.
@ -8465,6 +8465,6 @@ sd(log R̂(low₂) log R̂(low₁)) = 1.493 по n=19 ⇒ σ = 1.493/√
⛔ **И существеннее адресов: претензия тела «носитель решения врёт прямо сейчас» (про арм-конфиги) ЗАКРЫТА — носители ИСПРАВЛЕНЫ.**
Обоснования «×2 дешевле glm» в дереве больше нет ни в одном из двух файлов; на его месте пере-пин цен `D39.137` п.4:
`backend/configs/pipeline-c1.yaml:97`=`ПЕРЕВЁРНУТА пере-пином цен` и `backend/configs/pipeline-arm-glm.yaml:6`=`ПЕРЕВЁРНУТА пере-пином цен`
`backend/configs/pipeline-c1.yaml:99`=`ПЕРЕВЁРНУТА пере-пином цен` и `backend/configs/pipeline-arm-glm.yaml:6`=`ПЕРЕВЁРНУТА пере-пином цен`
— «в ПИК dspro дороже glm ×1.26, офф-пик дешевле ×1.59». Контроль: «дешевле» встречается в каждом файле РОВНО ОДИН раз,
и оба раза — про пере-пин; «×2» — ноль вхождений в обоих. ⇒ читающий тело НЕ должен заводить по этой претензии работу.

File diff suppressed because one or more lines are too long

View file

@ -59,7 +59,7 @@
Контрпример проверен исполнением: две книги различаются персонажем в предыдущей главе; следующая начинается одинаковым «终于答应了» — «наконец согласился/согласилась». Реальный `TranslateBook` с текущими zh→ru промптами и подставным провайдером дал разные запросы предыдущих единиц и побайтно одинаковые запросы текущих переводчика и редактора. Условия: пустой одинаковый банк, майнинг выключен, банкнота включена; две главы и две редакторские единицы на вариант. Это проверка доступности информации; частоту неправильных переводов живой модели она не устанавливает. Артефакт и воспроизведение — в конце документа.
Отрицательные результаты carryover не закрывают весь класс: в exp15 широкое чтение, lookahead и состояние сцены остались неисполненными (`docs/experiments/15-segmentation-empirics.md:489`=`не исполнены (бюджет/приоритет`). Сам target-architecture ограничивает перенос результатов классом исследованных книг (`docs/architecture/09-target-architecture.md:138`=`НИКОГДА не переносится в продукт`). Работа над структурой глав полезна, но она отвечает на другой вопрос.
Отрицательные результаты carryover не закрывают весь класс: в exp15 широкое чтение, lookahead и состояние сцены остались неисполненными (`docs/experiments/15-segmentation-empirics.md:489`=`не исполнены (бюджет/приоритет`). Сам target-architecture ограничивает перенос результатов классом исследованных книг (`docs/architecture/09-target-architecture.md:142`=`НИКОГДА не переносится в продукт`). Работа над структурой глав полезна, но она отвечает на другой вопрос.
**Коррекция:** проверить зависимости через границы редакторских единиц: субъект, адресат, намеренная неоднозначность, смена регистра, позднее раскрытие смысла термина. Сначала сравнить текущий вход с минимальным необходимым исходным контекстом; затем выбирать окно, поиск исходных фрагментов или состояние сцены. Не начинать со строительства универсальных summaries/графа. В строке 80 бэклога (`docs/PROGRESS.md:153`) уточнить: ссылка на исходник проверяется детерминированно, её семантическое толкование — нет. Требование безошибочного детерминированного толкования заранее заблокирует исследование этой задачи.

View file

@ -1646,10 +1646,10 @@ D39.50 (просьба владельца против «до него kwic не
§П3 утверждает: «Исхода движка „это не термин“ нет и не вводится. Класс отказ-сигналов закрыт D39.102 п.2». **Это верно
про НОТУ и неверно про ДЕРЕВО.** В боевом проводе zh-ru отказ-сигнал жив и приглашается самим промтом:
`backend/prompts/zh-ru/terminologist.md:36`=`не можешь выбрать перевод уверенно` («верни ⟦TM-NO-DST⟧… это нормальный»),
`backend/prompts/zh-ru/terminologist.md:32`=`не можешь выбрать перевод уверенно` («верни ⟦TM-NO-DST⟧… это нормальный»),
парсер ждёт его вместе с искажёнными формами (`backend/internal/terminology/terminology.go:694`=`const NoDst = "⟦TM-NO-DST⟧"`,
`backend/internal/terminology/terminology.go:1248`=`func isNoDst`), а счёт печатается прогоном
(`backend/internal/pipeline/terminologist.go:649`=`"declined", res.Declined`).
(`backend/internal/pipeline/terminologist.go:806`=`"declined", res.Declined`).
⚠ Различение, без которого поправка читается грубее, чем есть: §П3 прав в том, что у СТРОКИ БАНКА нет исхода «не термин» —
отказ роли не становится решением, а превращается в передачу черновика (это сам отчёт и говорит абзацем ниже). Неверно
@ -1691,7 +1691,7 @@ D39.50 (просьба владельца против «до него kwic не
номера строк, а в этот итог целятся якоря других доков.
**Уехал один адрес.** Счёт отказов терминолога печатается на
`backend/internal/pipeline/terminologist.go:702`=`"declined", res.Declined` — в теле стоит `:649`. Причина: пак «лестница
`backend/internal/pipeline/terminologist.go:806`=`"declined", res.Declined` — в теле стоит `:649`. Причина: пак «лестница
попытки» (акт `D39.264`) вынес цикл повторной попытки в `backend/internal/pipeline/attemptladder.go` и сдвинул строки файла.
**Утверждение тела при этом верно и не меняется** — отказ-сигнал жив в боевом проводе zh-ru, приглашается промтом роли и
считается прогоном; устарел только способ найти строку.