471 lines
52 KiB
Markdown
471 lines
52 KiB
Markdown
# ПАК «ФОРМАТЫ И ЧЕСТНЫЙ СЛОВАРЬ» — движковая сессия
|
||
|
||
> **ВЫДАН 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)`, `ingest.go:178`; ⚠-блок разбора — `:172-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`: пин снялся бы с другого разреза, и сравнивать
|
||
было бы нечего.
|
||
⛔ **И знай, КУДА они указывают: у ДВАДЦАТИ конфигов из 31 `langpack_root` смотрит в СОСЕДНЕЕ
|
||
РАБОЧЕЕ ДЕРЕВО, а оно на ДРУГОЙ ВЕТКЕ** (`/home/ubuntu/projects/textmachine`, ветка `polygon`);
|
||
ещё девять — в твоё (`main`), два не объявляют его вовсе. **§7 разрешает ЧТЕНИЕ соседнего дерева
|
||
по этим путям.** Перенаправлять нельзя (см. выше), писать туда нельзя.
|
||
⚠ **Прежняя редакция обосновывала это тем, что «деревья различаются, `ru/` есть только там» —
|
||
ЛОЖНО и снято:** замерено 05.09, каталоги `configs/langpacks` в обоих деревьях **байт-в-байт
|
||
идентичны** (`diff -rq` пуст, sha256 `heading.txt` совпадают). Комментарии в конфигах стенда,
|
||
утверждающие обратное, ПРОТУХЛИ — класс строки 299; править их не твоя зона, но и верить им
|
||
нельзя. Чтение разрешено по-прежнему, но не по этому доводу.
|
||
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.
|
||
⛔ **ПРО НОСИТЕЛЬ ГЕЙТА, иначе пин повиснет в воздухе:** конфигов `book.yaml` для 蛊真人 в дереве книг
|
||
ДЕСЯТОК, и **ни один не указывает на полную книгу** — все на срезы (`slice25`, `ch1-10`, `ch1-5`,
|
||
`ch4-5`). Полный `guzhenren-gb18030.txt` не привязан ни к одному конфигу.
|
||
⇒ **Гейт §3 = каждый РАЗЛИЧНЫЙ ИСХОДНИК воспроизводит свой разрез глав. Их ДЕВЯТЬ, а конфигов 31.**
|
||
Это не сужение, а верный предикат: **разрез ГЛАВ есть функция байтов исходника, кодировки и данных
|
||
структуры — от пар-пака он не зависит** (шапки ищутся по embedded `cjk-section.txt`, не по паре).
|
||
Конфиги, делящие исходник, дают тот же разрез по построению, и тридцать один пин мерил бы одно и то
|
||
же девять раз. ⚠ Тридцать один понадобился бы только для сравнения unit-id — а их из гейта вывела
|
||
находка 4.
|
||
Полную книгу (2283 главы) гоняешь ОТДЕЛЬНО, своим конфигом в своём каталоге; её результат идёт числом
|
||
в отчёт и гейтом НЕ является — базовой линии для неё не существует. ⚠ Платная книга платформы `bk_SS5VES2JELESJSTR` живёт в
|
||
workdir платформы, **гейту §3 не видна и в него не входит**; её заметки теряются по слову оркестратора.
|
||
|
||
### 2.2 Данные структуры — ПО ИСТОЧНИКУ, вне пар-пака
|
||
|
||
Ревью-вопрос проекта: «заработает ли пара, которой в репо НЕТ, без правки Go?»
|
||
|
||
- `marker`/`units`/выбор читателя номеров/грамматика шаблона → **файл структуры ИСТОЧНИКА**, свой
|
||
загрузчик по `Book.SourceLang`, **по образцу `reader.txt`**.
|
||
- ⛔ **В пар-пак класть НЕЛЬЗЯ:** книга без каталога пары идёт с nil-паком (`runner.go:397-407`).
|
||
- ⛔ **ОДИН ИСТОЧНИК НА ФАКТ — и дрейф здесь ЖИВОЙ, а не гипотетический.** Замерено 05.09:
|
||
`cjk-section.txt` (embedded, читает ИНГЕСТ) несёт **пять** юнитов `章节節回話`, а `zh-ru/heading.txt`
|
||
(пар-пак, читает ЧАНКЕР) — **четыре** `章节節回`; `parseHeading` кладёт `units` дословно и на
|
||
`CJKSection` не смотрит. ⇒ комментарий `ingest.go:184-185` («the SAME data … ONE source, no
|
||
ingest↔chunker byte-drift») **сам протух** — класс строки 299, поправить вместе с кодом.
|
||
⇒ **Пак не предотвращает возврат дрейфа, а ЗАКРЫВАЕТ живой**, и инвентарь — РЕШЕНИЕ, а не переезд.
|
||
**Ратифицировано: инвентарь — ПЯТЬ юнитов, источник `cjk-section.txt`.** Довод: пятый (話) добавлен
|
||
`D39.60` §3.1 и на нём стоит пин ja-детекции; ингест уже режет по пяти, отстал именно чанкер, и
|
||
сведение к пяти НЕ трогает разрез — меняется только `stripHeading`.
|
||
⚠ **И это обязано быть ЗАМЕРЕНО шагом 0:** встречается ли `話` шапкой в 蛊真人. Встречается —
|
||
меняются чанки и контент-хеши, и это идёт в ветку (б) гейта §3, а не проходит молча.
|
||
- ⛔ **Инвентарь скрипт-дефолта CJK берётся ИЗ `cjk-section.txt`, НИКОГДА из пар-файла.** У `ja`
|
||
каталога пары нет вовсе (`configs/langpacks/` — только `ru/`, `zh/`, `zh-ru/`), значит ja-книга
|
||
идёт по лестнице на скрипт-дефолт. Соберёшь дефолт из опустошаемого `zh-ru/heading.txt` — потеряешь
|
||
`話`, и `TestChapterUnitJaWebnovel` (`chunker_heading_test.go:73`) покраснеет НЕ заказанным.
|
||
- ⛔ **ОДИН ИСТОЧНИК НА ФАКТ.** Ингест И `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`) подвинется САМ — он идёт по полям рефлексией. ⚠ Ручной список живёт в соседнем
|
||
**`TestTheCutTagActuallyCarriesTheEngineData` (`cuttag_test.go:54-61`)** — новый компонент туда сам
|
||
не попадёт, добавляй руками.
|
||
|
||
**ФОРМА компонента — не на твоё усмотрение, она уже задана прецедентом:**
|
||
`Structure: <версия парсера> + sha256(байты структурных данных, РЕАЛЬНО загруженных для этой книги —
|
||
`<source>/structure.txt` либо дефолт по скрипту)`. **Никогда `omitempty`** — дефолт есть всегда. ⚠ И не копируй тег с `Langpack`
|
||
буквально: у него `omitempty` КАК РАЗ стоит, и в голдене `langpack_version` отсутствует вовсе. Бери
|
||
из его формы ПРИНЦИП — «считается одинаково на пишущем и читающем пути», — а не строку тега.
|
||
Считается ОДИНАКОВО на пишущем и читающем пути (`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` / грамматика шаблона — **и переезжают они из ПАР-ФАЙЛА
|
||
(`zh-ru/heading.txt`), а НЕ из `cjk-section.txt`.**
|
||
⛔⛔ **`internal/lang/data/cjk-section.txt` НЕ ПРАВИТСЯ ВООБЩЕ — ни одной строкой.** Он уже объявляет
|
||
себя единственным источником истины для разреза, и чанкер просто начинает читать ЕГО (через
|
||
`lang.DefaultCJKSection()`) вместо своей отставшей копии — **вот этим дрейф и закрывается.**
|
||
Тронешь файл — сдвинется `EmbeddedVersion`, а он тянет три вещи, каждая из которых тебя остановит:
|
||
голден несёт **четыре строки `embedded_version`** наравне с `chunker_version`;
|
||
`TestChapterMarkerIsData` (`langpack_test.go:454`) краснеет НЕ заказанным; и — главное —
|
||
**CURRENT-STATE (`docs/PROGRESS.md`) держит правило лендинга: правка `lang/data/*.txt` двигает
|
||
`EmbeddedVersion` → `cutTag`, и такой лендинг НЕ кладётся, пока жива книга платного прогона.**
|
||
Оставить там же мёртвые копии ключей тоже нельзя — это класс строки 299.
|
||
|
||
⛔⛔ **ЧТО ДЕЛАТЬ С ПАР-ФАЙЛОМ, КОТОРЫЙ ЖИВЁТ В ЧУЖОЙ ВЕТКЕ.** Вынув `marker`/`units` из
|
||
`zh-ru/heading.txt`, ты обязан решить, как `parseHeading` встретит СТАРЫЙ файл — а он останется
|
||
лежать в соседнем дереве на ветке `polygon`, куда тебе писать запрещено. **Ратифицировано:
|
||
парсер ОТВЕРГАЕТ `marker`/`units` с ЯВНЫМ переходным сообщением** («moved to source structure data;
|
||
remove them from heading.txt»), а не молча их глотает. Терпимость завела бы ровно тот мёртвый ключ,
|
||
который запрещён абзацем выше, и превратила бы громкий отказ в тихое расхождение.
|
||
⚠ **И знай цену, которую это назначает НЕ ТЕБЕ:** двадцать конфигов с корнем в `polygon` сломаются
|
||
не при твоём лендинге в `main`, а в тот момент, когда полигон **вольёт main в свою ветку** — до
|
||
этого он собирается из своего дерева со своим файлом и своим парсером. Это межветочная зависимость
|
||
данных, которую никто не проектировал. **Твоё дело — назвать её числом в отчёте; предупредить
|
||
полигон и завести строку — моё.**
|
||
|
||
### 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`) в ингесте, до нормализации**, и считается.
|
||
|
||
⛔ **ПОРЯДОК ОПЕРАЦИЙ — НАЗВАН ЯВНО, потому что наивный даёт ЦИКЛ и МОЛЧАЛИВУЮ ПОТЕРЮ ГЛАВЫ.**
|
||
Решить «есть ли шапки» до замены НЕЛЬЗЯ: перед `\f` нет `\n`, поэтому шапка сразу за ним лежит на
|
||
ОДНОЙ СТРОКЕ с прозой предыдущей главы — её отвергают сразу два предиката (длина строки против
|
||
`chapterHeaderMaxRunes`, и якорь `^` регэкспа). Заменить безусловно ДО детекции тоже нельзя —
|
||
уничтожается разделитель, нужный ветке отката. **Форма:**
|
||
**(1)** `\f` → `\n\n` на РАБОЧЕЙ КОПИИ; **(2)** детекция шапок идёт ПО КОПИИ; **(3)** шапки
|
||
победили → режем копию, книга `detected`, форм-фиды сочтены; **(4)** шапки проиграли → режем
|
||
**ОРИГИНАЛ** по `\f`, книга `delimited`.
|
||
⚠ **Существующий тест этого НЕ ловит:** он проверяет только `doc.Structure`, а при наивном порядке
|
||
ярлык остаётся верным (`detected`) — **глава теряется молча при зелёном тесте**. Это ровно тот класс,
|
||
который пак объявляет своим ⛔ (`D39.205`).
|
||
|
||
**Слабость выражается СЧЁТОМ, не классом:** `form feeds ignored: N`, `documents attached: M` — в WARN
|
||
и в отчёт, **НЕ в манифест**: потребителя на проводе у счёта сегодня нет, а поле без потребителя —
|
||
механизм ради галочки.
|
||
|
||
⚠ **Провенанс — свойство ГРАНИЦЫ.** Глав N, границ N−1; у первой главы открывающей границы нет.
|
||
⚠ **ПРАВИЛО ПРЕАМБУЛЫ ЗАМОРОЖЕНО:** текст до первой шапки сливается в главу 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 — и предмет сравнения назван точно:
|
||
**id глав и их номера**, ЛИБО
|
||
- **(б)** расхождение **перечислено**, приложена **машинная карта сдвигов**, в отчёте **ЯВНО названа
|
||
цена: что именно теряется и у кого**, и это **подписано владельцем**.
|
||
|
||
⛔⛔ **ЧТО В СРАВНЕНИЕ НЕ ВХОДИТ, иначе ветка (а) недостижима по построению.** `key` и **unit-id
|
||
ОБЯЗАНЫ сдвинуться** — каждый unit-id склеен как `chapterID:cutTag:idx` (`manifest.go:220-221`), а
|
||
§2.2 прямо ТРЕБУЕТ, чтобы компонент `Structure` вошёл в `cutInputs` и двинул `cutTag`; шапка того же
|
||
кода обещает «ANY re-cut mints new unit ids across the whole book» (`:218`). ⇒ полный JSON разойдётся
|
||
у КАЖДОГО конфига, и разойдётся **не на дефекте, а на работе, которую пак сам заказал**. Это не
|
||
расхождение, а исполнение §2.2, и одноразовая перечеканка id уже учтена отдельной ценой — строка
|
||
бэклога **298** прямо говорит, что она «другой предмет и другая цена».
|
||
⇒ **Сверяешь id глав и номера. `key` и unit-id — движутся, и это ожидаемо.**
|
||
|
||
⛔ **Почему не только (а).** Чистое «байт-в-байт» защищало бы ОШИБКИ нынешнего разреза: правильный
|
||
детектор чинит главы-огрызки, а гейт бы его за это не пустил. Это **девятая форма ложной зелени —
|
||
гейт, ЗАЩИЩАЮЩИЙ дефект** (`D39.202`). ⚠ Такого детектора **в паке 1 нет** (он в §5, пак 2) — довод
|
||
объясняет ФОРМУ гейта на будущее, а не заказывает тебе детектор.
|
||
|
||
⚠ **Ветка (б) честна ровно наполовину, и это записано сознательно:** мигратора окон банка нет, и пак
|
||
его не строит. Сдвиг окна = новый `TermID` = каскад на решения владельца в платформе. Поэтому (б)
|
||
обязывает НАЗВАТЬ потерю, а не починить.
|
||
|
||
⚠ **Поток подписи — не твой.** Ты сдаёшь карту и названную цену в отчёте; **оркестратор несёт
|
||
владельцу; лендинг — после его слова.** Не жди сам и не «подписывай» сам.
|
||
|
||
⚠ **ГЕЙТ ПРИМЕНЯЕТСЯ ТОЛЬКО К 蛊真人** — под ней подписанный банк владельца (окна в ординалах глав). **EPUB владельца
|
||
под гейт НЕ попадают: их разрез ОБЯЗАН измениться (spine → nav), это и есть заказ пака.** Для них
|
||
ветка (а) провалена по построению, и требовать под них подпись владельца бессмысленно. Они — прибор
|
||
§2.1 п.3: их новый разрез **ИЗМЕРЯЕТСЯ и идёт в отчёт числом**, а не гейтится.
|
||
|
||
✅ **ВЕТКА (а) ДЛЯ 蛊真人 ОБЕСПЕЧЕНА ЗАМЕРОМ, а не надеждой.** Шаг 0 сессии 05.09: форм-фидов
|
||
**НОЛЬ во всех шести файлах стенда**, значит ветка отката конкуренции не срабатывает нигде и разрез
|
||
боевой книги сдвинуть НЕЧЕМ. Там же: `話` в 蛊真人 — **ноль вхождений вообще**, поэтому сведение
|
||
чанкера к пяти юнитам не меняет ни одного её чанка и ни одного контент-хеша; ветка (б) по инвентарю
|
||
не нужна. ⚠ **Обратная сторона, и она про твою самопроверку:** раз `\f` в реальных книгах нет,
|
||
**путь конкуренции проверяется ИСКЛЮЧИТЕЛЬНО синтетическими фикстурами** — они здесь единственная
|
||
улика, и потому именованный артефакт §4 обязан целить именно туда, а не только в `nav`.
|
||
|
||
---
|
||
|
||
## 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. ⛔ **ЧИСЛО ГЛАВ, а не только ярлык, на фикстуре «шапка сразу за `\f`»** (`ingest_test.go:74`):
|
||
сегодня тест смотрит только `doc.Structure`, и потерянная глава проходит зелёной. Тест на СЧЁТ.
|
||
6. **Выравнивание `title_raw` с ПЛОТНОЙ нумерацией** — тест с ПУСТОЙ главой между двумя непустыми:
|
||
пустая глава номер не потребляет, а `title_raw` поедет параллельным срезом, и промах на одну
|
||
позицию даст заголовок главы N на главе N+1 **молча**. Тот же класс уже чинили для ruby
|
||
(`ingest.go:568-605`, `denseNo`).
|
||
7. **Именованный артефакт — ДВА, и второй важнее:** посади дефект в разбор `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» — **неверно и снято**: платная книга платформы
|
||
собрана из десятиглавого txt-среза, `nav` там нет вовсе, и сброса по томам в главах 1–10 быть не
|
||
может. Увидеть это можно только на ПОЛНОЙ 蛊真人, где нумерация и сбрасывается. Это выбор рендера (номер книги против плотного
|
||
ординала), он вне пака. Увидел — запиши числом в отчёт и иди дальше.
|
||
- **`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`. ⚠ **ЧТЕНИЕ соседнего
|
||
дерева разрешено там, куда указывают конфиги стенда** — прежде всего `backend/configs/langpacks`
|
||
(`langpack_root`): без этого §2.1 неисполним, потому что пин обязан сниматься ровно с тем
|
||
пар-паком, с которым книга резалась.
|
||
- **Тесты и гейты не подгонять под зелень.** ⚠ Правка, вызванная **заказанной** сменой поведения, —
|
||
обслуживание. **Заказанные здесь правки тестов, поимённо:**
|
||
· `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`**. ⚠ Уточнение, чтобы ты не искал несуществующего: **ассерт переезжает РОВНО ОДИН** —
|
||
тест сверяет только `doc.Structure`; у кейса с одним `\f` среди шапок ожидание то же и до, и
|
||
после, у него меняются объясняющий комментарий и ЧИСЛО глав (§4 п.5). ⚠ А у кейса `ingest_test.go:74` ожидание НЕ меняется (`detected` и до, и после) —
|
||
у него меняется **ЧИСЛО ГЛАВ**, которого никто не проверяет: см. §4 п.5. ⛔ И **само имя** `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` · что НЕ проверено
|
||
и почему.
|
||
|
||
⚠ **Споткнулся о невыполнимое, о противоречие в самом задании или о нехватку доступа — пиши сразу,
|
||
не обходи молча.** Обход тихо становится нормой, и следующая смена наследует его как факт.
|