Add the chapter-structure pack: formats and an honest provenance vocabulary, with the cut gated on the stand book reproducing byte for byte

This commit is contained in:
heaven 2026-09-05 21:30:06 +03:00
parent 75a586ae50
commit dba8a0bb76

View file

@ -0,0 +1,374 @@
# ПАК «ФОРМАТЫ И ЧЕСТНЫЙ СЛОВАРЬ» — движковая сессия
> **ВЫДАН 05.09 сессии `textmachine-main-fe`.** Три круга совместного ревью (оркестратор + Fable 5);
> состав пака и словарь — итог восьми кругов сходимости, тела разборов в `D39.202`/`D39.207`.
**Зона записи:** `backend/`. **Коммитит оркестратор** (`textmachine-main-27`), ты готовишь дерево и
передаёшь. **Отчёт:** секция «Бэкенд» в `docs/PROGRESS.md`. **Денег:** $0, ни одного платного вызова.
---
## 0. КАРТА ЧТЕНИЯ — ПЯТЬ ПОЗИЦИЙ, ОНА ЗАКОН
Дальше — только по её ссылкам. Голову журнала решений целиком НЕ читать (канон это запрещает).
1. **`backend/internal/chunk/ingest.go`** целиком — предмет пака живёт здесь.
2. **`docs/research/27-chapter-detection.md`** — через его ревью-шапку. Дизайн НЕ ратифицирован;
это материал, а не заказ.
3. **`## D39.205`** (пропущенный кусок — критическая поломка) и в `## D39.202` — **только п.3-бис**
(гейт умирает вытеснением) **и п.9** (чего не сторожит ничто). Тело ноты целиком не нужно.
**ПОДНОМЕР своего тела не имеет**`D2.3` живёт пунктом внутри `^## D2`.
4. Строки бэклога **297** (словарь провенанса) и **299** (имя, не разрешающееся ни во что) в
`docs/PROGRESS.md`.
5. **`docs/architecture/12-go-style-notes.md`** — норматив стиля, только больные места.
**Код читаешь по мере правки. Несущие файлы, которые ты ТОЧНО будешь править:**
`chunk/ingest.go` · `pipeline/manifest.go` (ключ, тег, `title_raw`) · `chunk/chunker.go`
(`stripHeading`, `matchHeaderLine`) · `lang/langpack.go` (⚠ `parseHeading` СЕГОДНЯ ТРЕБУЕТ `marker`,
`:677-679` — после «только `template`» он упадёт, это заказанная правка) · `lang/embedded.go`.
---
## 1. ЧТО УЖЕ ПОСТРОЕНО — НЕ ЗАКАЗЫВАТЬ ЗАНОВО
Самый дорогой раздел: половина ошибок прошлых паков — постройка второго носителя.
- **Провенанс разреза уже есть** — поле `Document.Structure`, и оно **спроектировано верно**:
отвечает на «КАКОЙ ПУТЬ провёл границы» (`ingest.go:94-95`). ⛔ Неверна его первая строка (`:70`),
рекламирующая «how much the chapter cut can be trusted», — и именно её списала платформа.
**Поле не переделывать; чинить словарь и формулировку.**
- ⛔ **Валидатор V2-7 «формат провёл ВСЕ границы» (`ingest.go:170-176`) — ЗАМЕЩАЕТСЯ конкуренцией
(§2.4), а не сохраняется рядом с ней.** После конкуренции его предикат
(`len(parts)>1 && len(chapters)==len(parts)`, `:177`) становится мёртвым кодом. Не пытайся
сохранить обе логики — это прямо противоречащие друг другу правила.
- **Доминирующий юнит с полом ≥2** для CJK уже есть (`detectChapterUnit`, `ingest.go:249-263`) —
это мини-индуктор, а не список паттернов. **Пол ≥2 — единственный валидатор, живущий в паке 1.**
- **Читательские слова цели** уже разведены и грузятся СВОИМ загрузчиком по ЯЗЫКУ, вне пар-пака
(`lang.LoadReaderWords`, `reader.go:87`) — **прецедент, по образцу которого делается структура
источника.**
- **Скрипт языка** уже выводится (`lang.LangScripts`, `script.go:67`), CJK-дефолт живёт данными.
---
## 2. ЧТО ЗАКАЗАНО
### 2.1 Шаг 0 — ПРИБОР ДО ПРАВОК. Поведение не меняется, $0
Без него пак не имеет права ложиться: под ним подписанный банк владельца (окна в ординалах глав)
и боевая книга.
1. ⛔ **Пин — ПОЛНЫЙ МАНИФЕСТ до правок**, а не хеш: id глав + номера (лучше весь JSON). Из хеша
не построить карту сдвигов §2.5, а она заказана. Хеш — производное, снимай и его.
**НО: под `books/` не запускай НИЧЕГО.** `tmctl manifest` пишет сайдкар рядом с проектной базой
книги (`manifestPath()` = `ProjectDB + ".manifest.json"`, `pipeline/manifest.go:189`), то есть
ПИШЕТ в каталог книги — а запись там запрещена (§7). **Форма: копируешь исходник и `book.yaml`
в собственный каталог сессии** (не `/tmp` — см. §4, не `books/`) **и все замеры и пины делаешь
на копии.**
**В копии обязательно перенаправь `project_db` и `source_file`.** `project_db` — ЯВНОЕ поле
yaml (`config/book.go:86`), и «рядом с yaml» подставляется, только если оно ПУСТО (`:199-201`);
у стендовых книг там абсолютные пути. Скопируешь как есть — сайдкар уедет обратно под `books/`
(запись, которая запрещена) или по мёртвому пути.
**А `langpack_root` / `langpack_extend` НЕ трогай** — они только читаются, и подмена дала бы
другой `Pack.Version()`, то есть другой `cutTag`: пин снялся бы с другого разреза, и сравнивать
было бы нечего.
2. **Синтетические фикстуры по стандартам:** EPUB 3 с `nav`, EPUB 2 с NCX. Сегодня тестовый билдер
пишет только `container→OPF→spine→xhtml` и **nav/NCX не пишет вовсе** (`chunktest/epub.go:42-82`).
3. **Оракул — КОД, не замер:** EPUB с `nav` разворачивается в txt, путь разреза обязан восстановить
nav-границы. Ground truth **без ручной разметки и без единого вызова модели**. ⚠ Сам ЗАМЕР — пак 2.
4. **Замеры ДО правок, каждый числом в отчёт:** есть ли `\f` в стенд-книгах · есть ли главы-огрызки
от print-TOC · сбрасывается ли нумерация по томам.
**Файлы стенда — поимённо** (см. §7 о доступе): 蛊真人 (GB18030 txt, ~2283 «第N节») · EPUB владельца
Кристофф/en · EPUB владельца isekai/ja. ⚠ Платная книга платформы `bk_SS5VES2JELESJSTR` живёт в
workdir платформы, **гейту §3 не видна и в него не входит**; её заметки теряются по слову оркестратора.
### 2.2 Данные структуры — ПО ИСТОЧНИКУ, вне пар-пака
Ревью-вопрос проекта: «заработает ли пара, которой в репо НЕТ, без правки Go?»
- `marker`/`units`/выбор читателя номеров/грамматика шаблона → **файл структуры ИСТОЧНИКА**, свой
загрузчик по `Book.SourceLang`, **по образцу `reader.txt`**.
- ⛔ **В пар-пак класть НЕЛЬЗЯ:** книга без каталога пары идёт с nil-паком (`runner.go:397-407`).
- ⛔ **ОДИН ИСТОЧНИК НА ФАКТ.** Ингест И `stripHeading` чанкера читают **тот же самый файл**.
Сегодня чанкер берёт `HeadingRule` пары; после разведения `heading.txt` оставляет ТОЛЬКО
`template`. Оставить marker в обоих местах = вернуть дрейф ingest↔chunker, который pair-14 закрыл
(«ONE source, no ingest↔chunker byte-drift», `ingest.go:181-186`).
- **Лестница при отсутствии данных:** язык → скрипт → embedded-дефолт ПО СКРИПТУ → честное `none`.
**Дефолт НЕЛЬЗЯ класть под `internal/lang/data/`**, хотя прецедент `cjk-section.txt` туда и манит:
`EmbeddedVersion()` хеширует **весь каталог** (`embedded.go:44-56`, `ReadDir("data")`; «a byte edit
of ANY embedded file is a loud --resnapshot»). Положишь туда — нарушишь «не в снапшот» МЕХАНИЗМОМ,
тест §4 п.3 покраснеет, и самый соблазнительный «фикс» будет хаком (руками исключить файл из
обхода). Клади в ОТДЕЛЬНЫЙ embed-каталог со своей версией (`internal/lang/structure/` или как
назовёшь).
- **Объём читателей номеров в паке 1 — РОВНО СУЩЕСТВУЮЩИЙ** (арабские + CJK). Схема данных пишется
под общий случай, но **римские/словесные читатели НЕ строятся** — их заказывает пак 2, если замер
покажет нужду.
**ВЕРСИОНИРОВАНИЕ — ТОЧНО, иначе воспроизводится F1 MAJOR (D39.122).** Данные структуры решают
ГРАНИЦЫ ⇒ обязаны войти в **`cutInputs`** (`manifest.go:240-255`) ⇒ двигают **`cutTag`** ⇒ двигают
**unit-id**. `manifestKey` фолдит компоненты ПОРОЗНЬ (`manifest.go:336-354`), и одного его
НЕДОСТАТОЧНО: правка данных перережет книгу, а id юнитов переживут — ровно тот дефект, ради которого
тег и заведён. **В снапшот волн — НЕ класть.** Тест `TestEveryCutInputMovesTheTag`
(`cuttag_test.go:26`) получает новый компонент.
**ФОРМА компонента — не на твоё усмотрение, она уже задана прецедентом:**
`Structure: <версия парсера> + sha256(байты структурных данных, РЕАЛЬНО загруженных для этой книги —
`<source>/structure.txt` либо дефолт по скрипту)`. **Никогда `omitempty`** — дефолт есть всегда.
Считается ОДИНАКОВО на пишущем и читающем пути (`runner.go:384-386`: «loaded on BOTH the write path
and the read-only path so status/export/report reproduce the identical snapshot»). Это ровно форма
`Langpack: r.packVersion()`.
**ЧТО НЕ ПЕРЕЕЗЖАЕТ, и это тихая ловушка:** **числительные** (`Zero`/`Digit`/`Unit`/`Magnitude`)
**остаются в `data/cjk-section.txt`**. Их читает не только ингест — на них стоят ДЕНЕЖНЫЕ $0-гейты
величины чисел (`checks/cheapgates.go:601, 636, 649, 666``lang.DefaultCJKSection()`), а это
ВЕРДИКТНЫЕ данные, и им место в снапшоте. Уносишь числительные — **молча меняются вердикты гейтов**.
Переезжают ТОЛЬКО `marker` / `units` / грамматика шаблона.
### 2.3 EPUB по стандарту
- Читать **`nav` (EPUB 3) и NCX (EPUB 2)**; spine — порядок ЧТЕНИЯ, а не оглавление.
- Учитывать `linear` и `properties` — сегодня `opfPackage` их не разбирает (`ingest.go:500-509`).
**Нормализация nav — правилами, а не на усмотрение** (research/27 — материал, не заказ):
цели сортируются **по позиции в spine** · цели вне spine или неразрешимые — игнор со счётом ·
вложенные `ol` **сплющиваются** (каждая цель — граница; иерархия томов — §5) · дубли целей
схлопываются · приоритет **nav → NCX → spine** · nav с нулём разрешимых целей = отсутствует.
**ТЕКСТ КНИГИ НЕ ТЕРЯЕТСЯ НИКОГДА** (`D39.205`: пропущенный кусок — критическая поломка):
- **служебные документы — НЕ текст книги и исключаются со счётом**, и список ТОЧНЫЙ, потому что
наивная форма не сработает: исключаются (1) документ с `properties="nav"`; (2) документы, чья роль — `toc`
или `cover` — объявлена **в landmarks (EPUB 3) ЛИБО в OPF `<guide>` через `<reference type=…>`
(EPUB 2)**. ⛔ Обе формы обязательны: landmarks — конструкция EPUB 3, **у EPUB 2 nav-документа нет
вовсе**, и сессия, проверившая только landmarks, приклеит `toc.xhtml` к главе 1 ровно на том
«массовом EPUB 2», которым это правило себя и мотивирует. ⚠ `properties="cover-image"` стоит на ИЗОБРАЖЕНИИ, а не на
документе, и среди документов spine не исключит ничего; landmarks — это `<nav epub:type="landmarks">`
ВНУТРИ nav-документа, в `properties` их нет. Грепнуть один `properties` и на этом успокоиться —
значит приклеить к главе 1 текстовую страницу оглавления (`toc.xhtml` в spine — массовый EPUB 2)
и титул. Больше НИЧЕГО не исключается;
- ⛔ **`linear="no"` НИКОГДА не начинает главу**, даже если nav на него указывает (сноски,
приложения) — прикрепляется к предыдущему по spine;
- **непокрытые контент-документы ДО первой цели nav** (титул, обложка-страница) прикрепляются к
**ПЕРВОЙ** главе — зеркало правила преамбулы txt;
- **остальные непокрытые** — к **предыдущей** главе;
- каждое прикрепление считается; число в WARN и в отчёт.
**Вырожденный nav — ОЖИДАЕМЫЙ исход пака 1, а не твой баг.** Скрейп с тремя целями на триста
документов даст три главы-кита с ярлыком `declared` и огромным счётом прикреплений. Словарь честен,
разрез — нет; лечение (валидаторы `runt`/`whale`, кросс-аудит) — пак 2 (§5). **Тревога здесь — СЧЁТ**,
и это всё, что от пака 1 требуется. Не лезь чинить кита.
### 2.4 Словарь: четвёртое значение и КОНКУРЕНЦИЯ свидетелей
**Словарь: `declared` · `delimited` · `detected` · `none`.**
- `declared` — формат утверждает СТРУКТУРУ ГЛАВ (nav/NCX).
- **`delimited` (новое)** — в файле был РАЗДЕЛИТЕЛЬ, но «это глава» — допущение движка. Сюда уходят
**и EPUB-spine, и `\f` в txt**: spine объявляет порядок чтения, form feed — разрыв СТРАНИЦЫ.
⚠ Реальный источник `\f` — не человек, а извлечение из PDF (`pdftotext` ставит его на каждом
разрыве страницы по умолчанию).
- `detected` — движок вывел границу из СОДЕРЖИМОГО.
- `none` — свидетеля нет (и при <2 главах уже так).
**КОНКУРЕНЦИЯ вместо сложения — заказанная СМЕНА ПОВЕДЕНИЯ.** Сегодня txt режется по `\f`, а
ПОТОМ каждая часть — по шапкам (`ingest.go:159-167`), пути СКЛАДЫВАЮТСЯ. Заказано: **есть шапки →
режем по шапкам, книга `detected`; шапок нет → режем по `\f`, книга `delimited`.** Смешанного случая
не остаётся **по построению**, агрегировать нечего, `structureOf` сводится к «`none` при <2 главах,
иначе свидетель победившего пути».
**И ГЛАВНОЕ — ЧТО С БАЙТАМИ ПРОИГНОРИРОВАННОГО `\f`.** `text.NormalizeSource` form feed **не
трогает** (`ingest.go:44-46`). Сегодня он до провода не доходит, потому что он и есть символ разреза;
после конкуренции проигнорированный `\f` **останется в тексте главы** и уедет модели, в контент-хеш
и в чекеры. ⇒ **игнорируемый `\f` заменяется на разрыв абзаца (`\n\n`) в ингесте, до нормализации**,
и считается.
**Слабость выражается СЧЁТОМ, не классом:** `form feeds ignored: N`, `documents attached: M` — в WARN
и в отчёт, **НЕ в манифест**: потребителя на проводе у счёта сегодня нет, а поле без потребителя —
механизм ради галочки.
**Провенанс — свойство ГРАНИЦЫ.** Глав N, границ N1; у первой главы открывающей границы нет.
**ПРАВИЛО ПРЕАМБУЛЫ ЗАМОРОЖЕНО:** текст до первой шапки сливается в главу 1, и код объясняет
зачем — «dense numbering aligns with the section numbers», «第一节» обязано быть главой 1
(`splitTextChapters`, `ingest.go:265-270`). Не трогать: правка сдвинет нумерацию ВСЕЙ стенд-книги.
⚠ Следствие, которое надо знать, а не чинить: у главы 1 шапка стоит НЕ в её начале, значит
`title_raw` главы 1 берётся из проглоченной шапки.
### 2.5 `title_raw` и карта сдвигов
- **`title_raw`** едет по проводу **аддитивно**. Определение: **строка шапки как в источнике** (txt)
/ **`navLabel` дословно** (EPUB). ⛔ Обоснование — собственное: заголовок, не едущий по проводу,
невозможно отрендерить. **НЕ ссылаться на «шесть заголовков из семнадцати»: у числа нет носителя**
(эррата 05.09-е).
- ⛔ **ПОНИЖЕНИЕ подзаголовка в прозу (`chunker.go:155-177`) НЕ ТРОГАТЬ.** Снимешь — изменятся чанки
и модель перестанет переводить подзаголовок. `title_raw` добавляется РЯДОМ, не вместо.
- ⚠ В EPUB-экспорт и в `nav` `title_raw` в этом паке НЕ входит — он на языке источника.
- ⛔ **БАМП `chunkerVersion` — ОДИН РАЗ, И ЭТО ЗАКАЗАНО ЯВНО.** Пак меняет РАЗРЕЗ (EPUB spine→nav,
конкуренция `\f`), а правило ингеста по конвенции покрывается `chunkerVersion` (`text/source.go`:
«covered by chunkerVersion (a deliberate re-translation, not a silent one)»). Забыть бамп —
оставить протухший манифест выглядящим текущим.
`chunker_version` уезжает в снапшот, и **голден краснеет: `testdata/golden/capture.golden`
несёт ЧЕТЫРЕ строки `chunker_version`** (проверено `grep -c`).
**Голден пере-захватывается ОДИН раз, в самом конце, и ТОЛЬКО после того, как ты предъявишь пин:
разрез самого голдена не изменился** (его фикстура — ja-txt с `第一章`, форм-фидов ноль).
Объявление по D39.183 обязательно.
⚠ Без этого абзаца ты упёрся бы в красный голден и по §7 обязан был бы остановиться и пинговать —
поэтому он здесь: правка ЗАКАЗАНА.
- **Карта сдвигов — МАШИННАЯ (JSON), set-diff по id и номеру: `same/moved/gone/new`.** Прозой нельзя:
на прозе не построить мигратор потом. Мигратор в пак НЕ входит — **пак производит УЛИКУ, а не
ремонт**, и это сказано вслух.
---
## 3. ГЕЙТ ЛЕНДИНГА
**Зелено, если выполнено ОДНО из двух:**
- **(а)** разрез стенд-книги воспроизводится **байт-в-байт** против пина §2.1, ЛИБО
- **(б)** расхождение **перечислено**, приложена **машинная карта сдвигов**, в отчёте **ЯВНО названа
цена: что именно теряется и у кого**, и это **подписано владельцем**.
**Почему не только (а).** Чистое «байт-в-байт» защищало бы ОШИБКИ нынешнего разреза: правильный
детектор чинит главы-огрызки, а гейт бы его за это не пустил. Это **девятая форма ложной зелени —
гейт, ЗАЩИЩАЮЩИЙ дефект** (`D39.202`). ⚠ Такого детектора **в паке 1 нет** (он в §5, пак 2) — довод
объясняет ФОРМУ гейта на будущее, а не заказывает тебе детектор.
**Ветка (б) честна ровно наполовину, и это записано сознательно:** мигратора окон банка нет, и пак
его не строит. Сдвиг окна = новый `TermID` = каскад на решения владельца в платформе. Поэтому (б)
обязывает НАЗВАТЬ потерю, а не починить.
**Поток подписи — не твой.** Ты сдаёшь карту и названную цену в отчёте; **оркестратор несёт
владельцу; лендинг — после его слова.** Не жди сам и не «подписывай» сам.
**ГЕЙТ ПРИМЕНЯЕТСЯ ТОЛЬКО К 蛊真人** — под ней подписанный банк владельца (окна в ординалах глав). **EPUB владельца
под гейт НЕ попадают: их разрез ОБЯЗАН измениться (spine → nav), это и есть заказ пака.** Для них
ветка (а) провалена по построению, и требовать под них подпись владельца бессмысленно. Они — прибор
§2.1 п.3: их новый разрез **ИЗМЕРЯЕТСЯ и идёт в отчёт числом**, а не гейтится.
**Ожидаемый исход для 蛊真人 — ветка (а).** В паке 1 её разрез может сдвинуть только
`\f`-конкуренция. Ветка (б) существует на случай, если `\f` там окажется.
---
## 4. САМОПРОВЕРКА — ИСПОЛНЕНИЕМ, И ВЕЕР НАПРАВЛЕН
Самоотчёт «проверено» без исполнения регулярно оказывается ложным. После кода — **адверсариальный
проход по СВОЕЙ готовой работе**. Глубину и веер выбираешь ты; направление — вот что уязвимо здесь:
1. ⛔ **Резка xhtml по якорям `nav`** (несколько глав в одном файле) — самое уязвимое место пака.
У `extractXHTML` **не меньше четырёх пинов байт-паритета** (`ingest_test.go:323, 359, 423, 467`:
void-теги, несбалансированный ruby, raw-text-элементы, голый амперсанд). Ошибка здесь ТИХАЯ,
и ломает она именно их.
2. **Загрузчик структуры обязан достигать книги БЕЗ пар-пака** — тест на книгу без `langpack_root`.
3. **Данные структуры реально двигают `cutTag`** (и через него unit-id) **и в payload `manifestKey` (`manifest.go:336-354`), и НЕ
двигают снапшот волн** — тест на каждое из трёх.
4. **Байты игнорируемого `\f`** — тест, что в тексте главы его не остаётся.
5. **Выравнивание `title_raw` с ПЛОТНОЙ нумерацией** — тест с ПУСТОЙ главой между двумя непустыми:
пустая глава номер не потребляет, а `title_raw` поедет параллельным срезом, и промах на одну
позицию даст заголовок главы N на главе N+1 **молча**. Тот же класс уже чинили для ruby
(`ingest.go:568-605`, `denseNo`).
6. **Именованный артефакт:** посади дефект в разбор `nav` так, чтобы одна глава потерялась молча,
и предъяви **имя покрасневшего теста**. Не краснеет ничто — это находка, а не повод двигаться.
**Каталог мутаций** (`backend/cmd/tmmutate`): `-root` обязан быть КОПИЕЙ; копия вне git ⇒
`GOFLAGS=-buildvcs=false`. **Не работай под `/tmp`:** посторонний `/tmp/.git` ломает `go build`
всему, что там собирается, и уже дал этой смене ложную зелень.
---
## 5. ГРАНИЦЫ — ЧЕГО В ЭТОМ ПАКЕ НЕТ
Названо, чтобы не открылось по ходу и не было сделано «по здравому смыслу»:
- **Лексиконы не-CJK, корпус, ЗАМЕР оракулом, сбор файлов, сеть** — пак 2. Пак 1 ничего не собирает.
- ⛔ **Структурные валидаторы (монотонность номеров, коридор hits/объём, headSpread, runt/whale) —
ПАК 2.** В паке 1 живёт только уже построенный пол ≥2. **Причина не стилистическая:** у стенд-книги
нумерация СБРАСЫВАЕТСЯ по томам (`eval/dovodka/narezka.py:18`), и валидатор монотонности отверг бы
или перекроил её шапки — то есть уронил бы гейт §3 на верной книге.
- **Индуктор скелетов строк** — пак 2, и только если замер покажет нужду.
- **Мигратор** окон банка, `chunk_status`, `unit_resolutions` — не здесь (строка 298, D15.2).
- **Идентичность по ИСХОДУ** (`cutTag` как дайджест нарезки) — свой пак. Здесь только сравнение
манифестов до/после в приборе шага 0.
- **Публикация провенанса по главе, `kind`, вердикт качества, иерархия томов, print-TOC-линковка,
`split/merged` в карте** — не строить. Потребителя нет, фронт заморожен.
- **FB2** — только если владелец скажет, что у беты есть FB2-источники.
- **Платформенное:** предикат «двух свидетелей» и `structure_version` · снятие временного правила
по `declared` (строка 297) · глагол «перечитать журнал» — **всё это НЕ твоя зона.** Не добавляй
полей «для платформы».
- ⛔ **Рендер при сбросе нумерации по томам — ВОПРОС ВЛАДЕЛЬЦА, не чинить.** Ты УВИДИШЬ в `nav`
платного EPUB несколько глав с одним номером. Это выбор рендера (номер книги против плотного
ординала), он вне пака. Увидел — запиши числом в отчёт и иди дальше.
- **`narezka.py` полигона** (второй приватный zh-детектор) — пинг полигону от оркестратора, не твоё.
- ⛔ **Сквозного прогона не-CJK НЕ БУДЕТ и не заказывается:** промтов пары для en/ja не существует
(`backend/prompts/` — только `zh-ru`). Не-CJK путь проверяется **только на уровне ingest/manifest,
$0**. Не пытайся «доказать бету» — не сможешь, и это не твой дефект.
---
## 6. ОБЯЗАТЕЛЬСТВО, КОТОРОЕ ПАК ДАЁТ ПЛАТФОРМЕ
**Пак НЕ бампает `manifestVersion`.** Значения и поля аддитивны; платформа игнорирует неизвестные
поля и деградирует на неизвестных значениях. Бамп понадобился бы, только если читатель прочтёт
документ **как текущий и НЕВЕРНО**: единица без плотного номера или с дубликатом · тотал, не равный
списку · пустой id · смена ФОРМЫ id.
**Граница обязательства: каждая единица манифеста нумерована ПЛОТНО и 1-based.** «Фрагмент» —
атрибут нумерованной единицы, а не отсутствие номера.
⚠ Контракт при этом обещает `number: null` как законный — и **шов его отвергает**; противоречие
записано эрратой 05.09 в `openapi.yaml`. Снимать его — не в этом паке.
**Стыковка с платформой — предусловие ЛЕНДИНГА и забота ОРКЕСТРАТОРА, не твоя** (`platform/`
чужая зона). От тебя нужно одно: **сдать в отчёте манифест-фикстуру со значением `delimited`**,
чтобы платформенный тест мог на ней проверить деградацию в «знаки».
---
## 7. ДИСЦИПЛИНА
- **Доступ к книгам.** `books/` — симлинк в соседнее дерево. **ЧИТАТЬ файлы под `books/` РАЗРЕШЕНО
и необходимо** (§2.1 без этого неисполним). ⛔ **ЗАПРЕЩЕНО:** любая запись под `books/`, любые
git-операции там, и что-либо ещё в `/home/ubuntu/projects/textmachine` вне `books/`.
- **Тесты и гейты не подгонять под зелень.** ⚠ Правка, вызванная **заказанной** сменой поведения, —
обслуживание. **Заказанные здесь правки тестов, поимённо:**
· `priceprojection_test.go:144` — кейс «a form feed is the format speaking» ждёт
`StructureDeclared`; **станет `delimited`**;
· тест V2-7 «формат провёл ВСЕ границы» — меняются ДВА кейса, не один: «один случайный `\f`
среди шапок» (шапки побеждают, книга `detected`) **и ПЕРВЫЙ кейс `ingest_test.go:68`
«the format drew every boundary» (`ГЛАВА A\fГЛАВА B``StructureDeclared`), который становится
`delimited`**. ⛔ И **само имя** `TestTXTStructureIsDeclaredOnlyWhenTheFormatDrewEVERYBoundary`
после конкуренции ни во что не разрешается — **переименовать тем же актом** (класс строки 299,
который этот же промт требует ниже для комментариев);
· `ingest_encoding_test.go:218-227` (GB18030, `第一章…\f第二章…`) — **счёт глав сохранит** (две
шапки побеждают), но его **комментарий про разрез по `\f` станет ложью** — поправить текст
комментария, это класс строки 299;
· ⚠ `TestIngestTXTFormFeedChapters` (`ingest_test.go:34-46`) проверяет разрез `A\fB\fC`, где шапок
нет — **при конкуренции НЕ меняется**, меняется только ярлык на `delimited`. Не «чини» его.
**Каждая такая правка ОБЪЯВЛЯЕТСЯ в отчёте:** что изменилось в поведении, какой тест это описывал,
куда уехала гарантия. Не заказано и не ратифицировано — **пинг, не правка**.
- **Комментарии, описывающие сменённое поведение, — тоже носители.** Правь их вместе с кодом; в
частности протухает `bookfile/epub.go:15-19` («ридер делает главу из КАЖДОГО документа spine»).
Это класс строки 299 — имя или утверждение, которое больше ни во что не разрешается.
- **Не хакать вокруг ошибки.** Нашёл дефект в чужой зоне — опиши, не чини.
- **Никогда не читать `.env`.** PUML не рендерить. **Не коммитить.** Не двигать HEAD. Не трогать
чужое незакоммиченное. ⚠ Якорный линт доков (`docs/scripts/counts.py --lint`) — гейт ОРКЕСТРАТОРА;
доки с якорями ты не пишешь, но знай, что он есть, и не строй второй такой.
- **Утверждение о замере — с КОМАНДОЙ** (`D39.207` п.5): пишешь «проверено» или «не проверено,
потому что X» — рядом стоит команда, которой это показано.
- ⛔ **Идентификатор, не виденный ЦЕЛИКОМ в выводе команды, не воспроизводить по памяти** — ни хеш,
ни номер строки. Обрезающий `cut`/`head` молча превращает улику в догадку (`research/29` §5.6:
так был сфабрикован sha256, совпавший на 52 знака из 64).
---
## 8. ЧТО ПРИСЛАТЬ И В КАКОМ ПОРЯДКЕ
1. **Эхо старта:** своими словами — что понял предметом и что считаешь границей. До кода.
2. **Записка-план** в отчёт: что делаешь, в каком порядке, и **чем сдача осмысленна, если тебя
оборвут на середине**. До кода.
3. **Замеры шага 0 — числами**, до правок.
4. Код.
5. **Отчёт:** батарея (команда, скипы числом, EXIT) · каталог · посадка именованного артефакта
(`file:line` · что подменено · **какое ИМЯ теста покраснело**) · объявления по D39.183 · счета
`form feeds ignored` / `documents attached` · манифест-фикстура с `delimited` · что НЕ проверено
и почему.
⚠ **Споткнулся о невыполнимое, о противоречие в самом задании или о нехватку доступа — пиши сразу,
не обходи молча.** Обход тихо становится нормой, и следующая смена наследует его как факт.