436 lines
39 KiB
Markdown
436 lines
39 KiB
Markdown
# Отчёт: пак «ФОРМАТЫ И ЧЕСТНЫЙ СЛОВАРЬ»
|
||
|
||
Сессия `textmachine-main-fe`, 05.09. Промт `docs/CHAPTER_STRUCTURE_SESSION_PROMPT.md` (выдан `dba8a0b`,
|
||
поправлен по находкам сессии: `24f02bb` · `23b00a3` · `5a502e7` · `cd55329`). **Денег потрачено $0**, платных
|
||
вызовов ноль: весь пак — путь `tmctl manifest` и тесты на фейковом провайдере.
|
||
|
||
---
|
||
|
||
## 1. ЧИСЛА
|
||
|
||
### Батарея
|
||
|
||
| | команда | EXIT | пакетов | FAIL | линтер | скипы |
|
||
|---|---|---|---|---|---|---|
|
||
| до правок | `make battery` | 0 | 19 | 0 | 0 issues | **3** |
|
||
| после | `make battery` | 0 | 19 | 0 | 0 issues | **3** |
|
||
|
||
Скипы поимённо и в обоих прогонах те же: `TestMinerFullBookParity` (корпусный, снимается `battery-stand`),
|
||
`TestHelperEventsRun`, `TestHelperKillLoop` (хелперы подпроцессов). **Новых скипов не появилось** — это
|
||
проверялось отдельно, потому что скип читается как зелень.
|
||
|
||
### Каталог мутаций
|
||
|
||
201 → **205 записей**. Шесть прогнаны исполнением на КОПИИ дерева (`~/tm-mut-0509`, `GOFLAGS=-buildvcs=false`),
|
||
**6 RED, 0 неожиданных исходов**:
|
||
|
||
| запись | покрасневший тест |
|
||
|---|---|
|
||
| `DF-competition-order-loses-a-chapter` | `TestTXTHeaderRightAfterAFormFeedIsNotLost` |
|
||
| `EPUB-preamble-joins-chapter-one` | `TestEPUBUncoveredBeforeFirstTargetJoinsChapterOne` |
|
||
| `EPUB-service-pages-are-exactly-two-roles` | `TestEPUB3LandmarksExcludeTocAndCover`, `TestEPUB2GuideExcludesTocAndCover` |
|
||
| `PP-structure-provenance` (пере-нацелена) | `TestTheCutSaysWhetherItWasReadOrGuessed` + два подтеста |
|
||
| `TITLE-raw-follows-the-kept-index` | `TestTitleRawSurvivesAChapterThatTakesNoNumber` |
|
||
| `EPUB-collapsed-targets-downgrade-the-provenance` | `TestEPUBDuplicateNavTargetsCollapse` |
|
||
|
||
⚠ Полный прогон всех 205 записей НЕ делался — см. §6.
|
||
|
||
### Замеры шага 0
|
||
|
||
| # | замер | число |
|
||
|---|---|---|
|
||
| 1 | `\f` в стенд-книгах | **0** во всех шести файлах |
|
||
| 2 | главы-огрызки от print-TOC в 蛊真人 | **0** (мин. глава 1804 знака, медиана 3289) |
|
||
| 3 | сброс нумерации по томам | **есть**: 2283 шапки → 945 уникальных номеров, кратность до 6 |
|
||
| 4 | шапки `第N节` | ровно **2283**; 章/節/回/話 — по нулю |
|
||
| 5 | `話` в 蛊真人 | **0** вхождений вообще |
|
||
| 6 | преамбула до первой шапки | 3130 знаков |
|
||
| 7 | `declared` во всём стенде до пака | **0** (семь `detected`, один `none`) |
|
||
|
||
Замер 1 значит, что конкуренция §2.4 физически не могла сдвинуть разрез боевой книги. Замер 5 — что сведение
|
||
чанкера к пяти юнитам не тронуло ни одного её чанка.
|
||
|
||
### Счета формата
|
||
|
||
`form feeds ignored`, `documents attached`, `documents excluded`, `toc unresolved`, `targets collapsed` живут в `chunk.Document`,
|
||
уезжают в WARN (`events.go`, `ingestSource`) и **НЕ на провод** — потребителя у них сегодня нет.
|
||
|
||
---
|
||
|
||
## 2. ГЕЙТ ЛЕНДИНГА — ВЕТКА (а), ВОСЕМЬ ИЗ ВОСЬМИ
|
||
|
||
Гейт сверяет **id глав и номера** (ратифицировано находкой 4). Пины сняты до правок и после, тем же
|
||
прибором, конфигами сессии в `~/tm-pins-0509`.
|
||
|
||
| исходник | sha256[:16] | глав | structure | разрез |
|
||
|---|---|---|---|---|
|
||
| `ch1` | `eda401ef10edbf40` | 1 | none | байт-в-байт |
|
||
| `ch1-2` (= `coldrun-b`) | `eb87729061be6d39` | 2 | detected | байт-в-байт |
|
||
| `ch4-5` | `33dc2b5b7e491435` | 2 | detected | байт-в-байт |
|
||
| `ch1-5` | `8755afd3e6aab298` | 5 | detected | байт-в-байт |
|
||
| `s11-16` | `5c22ec0ddaf19bb0` | 6 | detected | байт-в-байт |
|
||
| `ch1-10` | `0b5f9b0266d8c32a` | 10 | detected | байт-в-байт |
|
||
| `ch26-42` | `1ebcc9efb0fa5006` | 17 | detected | байт-в-байт |
|
||
| `slice25` | `78f4cb7d990f5e1a` | 25 | detected | байт-в-байт |
|
||
|
||
**Двинулось ровно заказанное** (пример `ch1-10`): `key` `1e62a68d93f73534…` → `01a00e3669302bc1…`;
|
||
unit-id главы 1 `c3aa4cbbb53de134:d4fb75e2:0` → `c3aa4cbbb53de134:1b30b1dd:0` — **префикс id главы
|
||
неподвижен, уехал сегмент `cutTag`**. Это и есть §2.2, исполненный буквально.
|
||
|
||
⚠ **Носитель гейта — восемь ИСХОДНИКОВ, а не 31 конфиг**, и это не сужение: разрез глав есть функция байтов
|
||
исходника, кодировки и данных структуры. Проверено ИСПОЛНЕНИЕМ до правок: тот же исходник под другим
|
||
конфигом (другой пайплайн, другой `book_id`) дал идентичные id и номера глав, разошлись только `key` и
|
||
`cutTag`.
|
||
|
||
---
|
||
|
||
## 3. ЧТО ПОСТРОЕНО
|
||
|
||
1. **`internal/lang/structure.go`** — данные структуры ИСТОЧНИКА: тип `SourceStructure`, загрузчик
|
||
`LoadSourceStructure` с лестницей (`<root>/<язык>/structure.txt` → CJK-дефолт по скрипту → nil),
|
||
отпечаток для тега разреза. Загрузчик отдельный от пар-пака и достигает книги БЕЗ него.
|
||
⚠ **Файл `zh/structure.txt` НЕ создан намеренно:** он дублировал бы `cjk-section.txt` и завёл бы ровно тот
|
||
второй носитель, который пак закрывает. Механизм при этом работает: новая пара кладёт свой файл, Go не
|
||
правится.
|
||
2. **Живой дрейф закрыт.** Было: ингест — пять юнитов из `cjk-section.txt`, чанкер — четыре из
|
||
`zh-ru/heading.txt`. Стало: одно резолвленное значение на обоих путях. `heading.txt` оставил только
|
||
`template`; `parseHeading` отвергает `marker`/`units` громко, называя, куда уехал факт.
|
||
`cjk-section.txt` НЕ тронут ни одной строкой.
|
||
3. **Компонент `Structure`** в `cutInputs` и в payload `manifestKey`, без `omitempty`.
|
||
4. **Конкуренция свидетелей** и значение `delimited`. Порядок ратифицированный: `\f`→`\n\n` на рабочей
|
||
копии → детекция по копии → откат на ОРИГИНАЛ, если шапки проиграли.
|
||
5. **EPUB `nav`/NCX** (`internal/chunk/epubtoc.go`): приоритет nav → NCX → spine; сплющивание вложенных
|
||
`ol`; схлопывание дублей; цели вне spine — игнор со счётом; nav с нулём разрешимых целей = отсутствует;
|
||
`linear="no"` не открывает главу; служебные страницы исключаются по `properties="nav"`, landmarks (EPUB 3)
|
||
И OPF `<guide>` (EPUB 2); непокрытые до первой цели — в главу 1, прочие — в предыдущую.
|
||
6. ⛔ **Схлопнутые цели ПОНИЖАЮТ провенанс.** Книга, пакующая несколько глав в один документ с якорями,
|
||
объявляет больше глав, чем пак отдаёт: он группирует документы целиком и по якорям не режет. Назвать такой
|
||
разрез `declared` значило бы выдать своё, более грубое, за утверждение формата — та же ложь, что говорил
|
||
spine, в новом месте. Поэтому при `targets collapsed > 0` книга идёт как **`delimited`**, со счётом в WARN.
|
||
Четырёхзначного словаря для этого хватило: достраивать ничего не пришлось.
|
||
⚠ Резка xhtml по якорям в пак НЕ входит — отдельный заказ; цена названа: текст цел, гранулярность
|
||
потеряна.
|
||
7. **`title_raw`** аддитивно: строка шапки как в источнике (txt) / `navLabel` (EPUB). Понижение подзаголовка
|
||
не тронуто.
|
||
8. **Бамп `chunkerVersion`** v6 → v7 и пере-захват голдена — один раз, последним действием.
|
||
|
||
### Ревью-вопрос проекта — закрыт исполнением
|
||
|
||
«Заработает ли пара, которой в репозитории НЕТ, без правки Go?» — **да**, и это тест, а не утверждение.
|
||
`TestANewLanguageCutsFromItsDataFileAlone` (`internal/chunk`) проверяет ОБЕ половины на одном тексте и одном
|
||
бинаре: корейская книга без своего файла остаётся **одной** главой (CJK-дефолт даёт маркер 第, которого в
|
||
корейском нет), а с `ko/structure.txt`, объявляющим 제 и 장, режется на **три**, отдаёт `detected` и несёт
|
||
`title_raw` = `제2장 전개`. Единственная разница между половинами — файл данных.
|
||
Половина на уровне загрузчика — `TestALanguageAbsentFromTheRepositoryCutsFromDataAlone` (`internal/lang`),
|
||
она же пинует, что отпечаток берётся из ФАЙЛА, а не из дефолта: иначе правка чужой грамматики не пере-резала
|
||
бы книгу.
|
||
|
||
⚠ **ГРАНИЦА, КОТОРУЮ ЭТОТ ЖЕ ТЕСТ ВСКРЫЛ, и она честнее ответа «да».** Ингест режет только по грамматике
|
||
С ЮНИТОМ: `detectChapterUnit` итерирует `UnitOrdered`, и язык без юнита («Kapitel 12») сегодня НЕ режется —
|
||
хотя ЧАНКЕР такую грамматику уже умеет, и на это есть живой тест `TestStripHeadingGenericLatinMarker`
|
||
(`matchHeaderLine` юнита не требует). То есть половина не-CJK пути готова, а половина нет, и данные это
|
||
выразить могут, а детектор — нет. Не достраивал: §2.2 прямо говорит «римские/словесные читатели НЕ
|
||
строятся», объём читателей в паке 1 — ровно существующий. **Ревью-вопрос закрыт для языков С юнитом и
|
||
открыт для языков без него** — это кандидат в строку бэклога, не тихий пробел.
|
||
|
||
---
|
||
|
||
## 4. ОБЪЯВЛЕНИЯ ПО D39.183
|
||
|
||
Правки тестов, вызванные ЗАКАЗАННОЙ сменой поведения:
|
||
|
||
- `ingest_test.go` — кейс `"the format drew every boundary"` (`ГЛАВА A\fГЛАВА B`) ждал `StructureDeclared`,
|
||
стал `delimited`. **Единственный переехавший ассерт.** Сам тест переименован:
|
||
`TestTXTStructureIsDeclaredOnlyWhenTheFormatDrewEVERYBoundary` → `TestTXTProvenanceNamesTheWinningPath` —
|
||
прежнее имя после конкуренции ни во что не разрешается (класс строки 299).
|
||
- `priceprojection_test.go` — кейс `"a form feed is the format speaking"` → `"a form feed is a separator, not
|
||
a chapter"`, ожидание `Declared` → `Delimited`.
|
||
- `ingest_encoding_test.go` — счёт глав сохранён (шапки победили), поправлен комментарий и текст ошибки:
|
||
разрез там больше не по `\f`.
|
||
- `chunker_heading_test.go` — `zhRuHeadingRule()` → `zhRuChapterRule()`, инвентарь больше не переписан
|
||
руками, а берётся из `DefaultCJKStructure()` (это и есть закрытие дрейфа).
|
||
- Механические правки сигнатур (`IngestEncoded` + структура, `SplitChunksWithChapters` + `keptIdx`,
|
||
`denseChapters`) в `bookbuild_test.go`, `contractblockers_test.go`, `chunker_manifest_test.go`,
|
||
`cuttag_test.go`.
|
||
- **Каталог:** `DF-declared-means-every-boundary` целила в предикат V2-7, который §1 приказал УДАЛИТЬ.
|
||
Заменена на `DF-competition-order-loses-a-chapter` — свойство, занявшее его место. `PP-structure-provenance`
|
||
пере-нацелена на новый `structureOf`. Обе прогнаны красными.
|
||
|
||
**Гарантия никуда не «уехала»:** свойство «стрей `\f` среди шапок не делает разрез `declared`» стало
|
||
недостижимым ПО ПОСТРОЕНИЮ (смешанного случая нет), а на его место встало свойство порядка операций, у
|
||
которого теперь есть и тест, и запись каталога.
|
||
|
||
---
|
||
|
||
## 5. НАХОДКИ ДО КОДА (все приняты оркестратором и внесены в промт)
|
||
|
||
1. **§2.4 заказывал цикл.** Решить «есть ли шапки» нельзя до нейтрализации `\f`: шапка сразу за ним лежит на
|
||
ОДНОЙ строке с прозой предыдущей главы (204 руны, начинается с 蛊) и отвергается двумя предикатами —
|
||
`ingest.go` длина > `chapterHeaderMaxRunes` и якорь `^`. Дороже цикла то, что **существующий тест этого
|
||
не ловил**: он проверял только `doc.Structure`, а ярлык при наивном порядке остаётся ВЕРНЫМ.
|
||
2. **Премисса «дрейф закрыл pair-14» неверна** — дрейф был живой: 章节節回話 против 章节節回.
|
||
3. **Риск красного незаказанного теста** `TestChapterUnitJaWebnovel` при сборке дефолта из пар-файла.
|
||
4. **Гейт был недостижим по построению:** требовал байт-в-байт манифест, а пак сам заказывает сдвиг
|
||
`cutTag`, перечеканивающий все unit-id (`manifest.go`, `manifestUnitID`).
|
||
5. **Пар-пак в чужой ветке:** `langpack_root` 20 конфигов из 31 указывает в рабочее дерево ветки `polygon`.
|
||
6. **Стенд отстал от движка:** стенд-конфиг даёт манифест ровно для 1 исходника из 8 (`{{genre}}` ретирован
|
||
и живёт в шести промтах polygon; `mined_delta`/`mined_rejects` ретированы и стоят в 17 конфигах из 31;
|
||
поля `stm_depth`/`overlap_tokens` исчезли).
|
||
|
||
### Находки о СВОЁМ коде, пойманные собственными тестами
|
||
|
||
- **`Marker` был одновременно классом рун и литеральным префиксом** — тот же дрейф в новой форме. «Chapter»
|
||
сортировался в набор букв. Исправлено моделью альтернатив + `regexp.QuoteMeta`.
|
||
- **Файловый парсер был СТРОЖЕ движка**: требовал `units`, хотя безъюнитная грамматика — ровно не-CJK случай,
|
||
ради которого пак и делается.
|
||
- **Группировка не приклеивала преамбулу**, а открывала ей отдельную главу.
|
||
- **Граница главы выводилась «первый или последний элемент группы»** — хрупко; сделана явным полем.
|
||
|
||
### Выжившая посадка, записанная с аргументом
|
||
|
||
Переворот сортировки `starts` в `resolveTOC` **не покрасил ничего**. Причина: `starts` читается как
|
||
МНОЖЕСТВО, группировка идёт по spine, и порядок целей на выход не влияет. Сортировка оставлена (она держит
|
||
истинным объявленный порядок поля), но комментарий теперь говорит это прямо, а не утверждает, что цели
|
||
«упорядочены по позиции в spine» как о несущем свойстве.
|
||
|
||
---
|
||
|
||
## 6. ЧТО НЕ ПРОВЕРЕНО И ПОЧЕМУ
|
||
|
||
- **Полный каталог (205 записей) не гонялся.** Прогнаны 6 записей, целящих в изменённое поведение. Причина —
|
||
цена: 124 записи гоняют `./internal/pipeline/`, чей набор идёт ~145 с, то есть полный прогон — часы при
|
||
загруженном хосте. Механически проверено другое и дешёвое: **у всех 205 записей цель ЖИВА** (греп `find`
|
||
по дереву), то есть протухших целей после пака не осталось.
|
||
- **EPUB владельца (Кристофф/en, isekai/ja) не прогонялись через новый путь.** §3 выводит их из гейта, а
|
||
§2.1 п.3 требует ОРАКУЛА, а не замера; оракул построен кодом (`TestEPUBNavOracleReproducesTheTxtCut`).
|
||
Замер их нового разреза числом — не сделан.
|
||
- **Платная книга платформы** не трогалась — чужая зона.
|
||
- **Сквозного прогона не-CJK нет** — промтов пары для en/ja не существует (§5), проверка ограничена
|
||
уровнем ingest/manifest, $0.
|
||
- **`counts.py --lint`** не запускался — гейт оркестратора, доков с якорями я не писал.
|
||
- **Полная 蛊真人 (2283 главы)** через новый путь не прогонялась: у неё нет конфига, а её результат по §2.1
|
||
идёт числом в отчёт и гейтом не является.
|
||
|
||
---
|
||
|
||
## 7. ФИКСТУРА `delimited` ДЛЯ ПЛАТФОРМЫ (§6)
|
||
|
||
`~/tm-pins-0509/delimited/manifest.json` — реальный манифест, снятый движком:
|
||
`"structure": "delimited"`, три главы, `title_raw: ""` у каждой (источник заголовков не называет).
|
||
Источник — кириллический txt с двумя `\f` и без единой шапки: шапки проигрывают, побеждает разрыв страницы.
|
||
|
||
⚠ Фикстура лежит ВНЕ репозитория. Если она нужна платформенному тесту как файл — это решение оркестратора,
|
||
куда её положить: `platform/` мне не зона.
|
||
|
||
---
|
||
|
||
## 8. ДОФИКС ПО ПРИЁМКЕ (06.09)
|
||
|
||
Две линзы приёмки нашли шесть обязательных и пять сопутствующих. **Все проверены мной заново перед починкой
|
||
и все подтвердились** — ни одна не отклонена.
|
||
|
||
### Обязательные
|
||
|
||
| # | что было | чем закрыто |
|
||
|---|---|---|
|
||
| M-1 | роль `toc`/`cover`, объявленная С ФРАГМЕНТОМ (`c2.xhtml#pos`), исключала ВЕСЬ документ ⇒ молчаливая потеря прозы | объявление на ТОЧКЕ внутри документа больше не исключает файл; отказ считается (`ServiceRolesInsideDocuments`). Тесты `TestEPUBServiceRoleWithAFragmentDoesNotDeleteTheDocument` + обратный `…WholeDocumentServiceRoleStillExcludes` |
|
||
| M-2 | три счёта из пяти не доходили никуда; комментарий обещал WARN | ОДИН репортёр `Document.IngestNotes()` — и рефлексионный сторож `TestEveryIngestCountReachesTheAlarm`, который краснеет на любом новом неподключённом счётчике |
|
||
| M-3 | `manifestKey` не пинован ничем | `TestTheManifestKeyCarriesTheStructureGrammar`; проверен ровно мутацией верификатора |
|
||
| M-4 | EPUB-половина `title_raw` не покрыта; метки фикстур вырождены (`e`/`p` всем) | метки стали различимыми и НАЗЫВАЮТ свой источник (`NAV …`/`NCX …`); три теста, включая «заголовок берётся у ГРАНИЦЫ, а не у вклеенной впереди страницы» |
|
||
| M-5 | `unresolved` рос и на прекрасно разрешившихся служебных/нелинейных целях ⇒ любой обычный EPUB 2 отчитывался «сломанным оглавлением» | счётчик считает ровно то, что обещает; `TestEPUBOrdinaryBookDoesNotReportABrokenTOC` |
|
||
| M-6 | `mutations.json` переформатирован целиком (дифф 5080 строк) | отступ возвращён к 1; **дифф стал 62 строки**, правка видна |
|
||
|
||
### Сопутствующие
|
||
|
||
`m-5` — комментарий `bookfile/epub.go` был заказан §7 поимённо и мной пропущен; переписан. `m-6` — снят
|
||
моей же правкой M-2. `m-7` — «all three values» при четырёх (сказано, где пинуется `declared`) и шапка
|
||
`stripHeading` про `HeadingRule`/nil (условие no-op — ПУСТОЙ шаблон). `m-10` — счёт nav больше не теряется
|
||
при переходе на NCX; `TestEPUBDanglingNavCountSurvivesTheFallToNCX`. `L1` — два резолвера href сведены в
|
||
`hrefTarget`; **прежний `resolveHref` был неверен**: раскодировал ДО среза фрагмента, значит литеральный `#`
|
||
(приезжающий как `%23`) обрезал имя файла.
|
||
|
||
### m-8 — ОБЪЯВЛЕНИЕ ПО D39.183, замеренное
|
||
|
||
Регэксп ингеста стал допускать пробел между маркером и цифрой (`第 1章`), прежний не допускал. Это
|
||
одностороннее расширение, заказом не покрытое: сделано, чтобы ингест и чанкер читали заголовок ОДИНАКОВО —
|
||
`matchHeaderLine` пробел терпел всегда. **Цена замерена:**
|
||
|
||
| файл | старый регэксп | новый | разница |
|
||
|---|---|---|---|
|
||
| 蛊真人 (полная, 215 675 строк) | 2996 | 2996 | **0** |
|
||
| фикстура голдена | 8 | 8 | **0** |
|
||
| `isekai_majutsushi_jp.txt` | 4 | 4 | **0** |
|
||
| `enkan_no_hate_ja.txt` | 0 | 0 | **0** |
|
||
|
||
### Вырожденный разрез EPUB владельца — ЧИСЛОМ, замер мой
|
||
|
||
Прогнал все три EPUB через новый путь (ингест, $0; `tmctl manifest` на них не идёт — промтов `en-ru`/`ja-ru`
|
||
не существует, §5):
|
||
|
||
| книга | глав | structure | знаков min / медиана / max | глав < 200 знаков | счета |
|
||
|---|---|---|---|---|---|
|
||
| Kristoff (en) | 71 | declared | 18 / 29 / 397 878 | **64** | attached 54 · excluded 1 · **service-role-inside-doc 1** |
|
||
| isekai (ja) | 44 | declared | 72 / 6669 / 8999 | 9 | excluded 1 |
|
||
| fifty_shades (en) | 32 | declared | 0 / 32 276 / 45 511 | 3 | excluded 2 |
|
||
|
||
⛔ **Цена Kristoff названа прямо:** 64 единицы манифеста из 71 несут не текст книги, а строки её заднего
|
||
листинга, и пойдут в оплату как главы; окна банка получат на этой книге гранулярность 6. По словарю ярлык
|
||
честен (`nav` действительно объявил эти цели), и §2.3 называет такой исход ожидаемым для пака 1 — но цена
|
||
существует и это вопрос владельца, а не мой.
|
||
|
||
⚠ И `service-role-inside-doc 1` у Kristoff — **это ровно случай M-1 на живой книге**: там роль `toc`
|
||
объявлена с фрагментом, и до починки этот документ был бы вычеркнут целиком.
|
||
|
||
`title_raw` заполнен у **71 из 71**, **44 из 44** и **32 из 32** глав — EPUB-половина работает на реальных
|
||
книгах, а не только на фикстурах.
|
||
|
||
---
|
||
|
||
## 9. ВТОРОЙ КРУГ ДОФИКСА — И ОДНА ПОПРАВКА К ДОВОДУ, КОТОРЫЙ ЕГО ЗАКАЗАЛ
|
||
|
||
### Д-1 — ИНВАРИАНТ ПОСТРОЕН, но его область УЖЕ, чем было заявлено
|
||
|
||
Требование верное и принято: счета — это наблюдаемость, инвариант — гарантия. Построен и проверяется ДО
|
||
любого платного пути, нарушение — громкая ошибка движка:
|
||
|
||
> каждый читаемый документ spine либо исключён НАМЕРЕННО, либо попадает ровно в одну главу;
|
||
> и глава — это ровно её документы плюс вставленные между ними разделители.
|
||
|
||
⚠ **Но довод, которым он заказан, я обязан поправить, потому что проверил обе половины посадками.** Было
|
||
сказано: «инвариант ловит потерю текста ЛЮБЫМ путём — сегодняшним фрагментом и завтрашним неизвестным». По
|
||
замеру это не так:
|
||
|
||
| посадка | что делает | ловит ли инвариант |
|
||
|---|---|---|
|
||
| A | группировка молча роняет документ | **ДА** — `spine document "OEBPS/c1b.xhtml" is neither excluded nor part of a chapter … book text would be lost silently` |
|
||
| B | возврат дефекта M-1 (роль с фрагментом исключает файл) | **НЕТ** — падают только собственные тесты M-1 |
|
||
|
||
Причина простая: исключение **учтено**, поэтому сумма сходится. Инвариант ловит текст, ушедший из чтения
|
||
БЕЗ решения; неверное, но записанное решение он по построению увидеть не может. Это не довод против него —
|
||
класс, который он закрывает, счетами не ловился вовсе. Но «ловит любым путём» под ним не стоит.
|
||
|
||
### Д-2 — МОЙ ДОФИКС M-1 ОБМЕНЯЛ ПОТЕРЮ НА ШУМ, и на книге владельца это регресс
|
||
|
||
Принято целиком. До починки документ Kristoff, на который роль `toc` указывает с фрагментом, исключался
|
||
ЦЕЛИКОМ — и там это случайно оказалось верно: файл и есть чистое оглавление. После починки он **прикреплён
|
||
как текст книги** (`service-role-inside-doc 1` при `excluded 1`).
|
||
|
||
⛔ **Цена прямым текстом: листинг оглавления уедет модели, будет оплачен и покажется читателю внутри главы.**
|
||
Относительно состояния ДО дофикса на ЭТОЙ книге стало хуже; относительно общего случая — лучше, потому что
|
||
раньше терялась проза, а по `D39.205` потеря критична, а шум нет.
|
||
|
||
**Узкий ремонт (текст до якоря — книга, от якоря — служебное) НЕ делался**, и это осознанный отказ: он и есть
|
||
резка xhtml по якорям, которую пак выводит за границы и под которой лежат четыре пина байт-паритета
|
||
`extractXHTML`. Изобретать её в конце пака — та самая сделка, от которой пак предостерегает. Кандидат в пак 2.
|
||
|
||
### Д-3 — ТРЕВОГА ПОСТРОЕНА, НО ПОЛЬЗОВАТЕЛЮ БЕТЫ НЕ ВИДНА
|
||
|
||
`IngestNotes()` идёт в `r.Log.Warn`, то есть в лог ДВИЖКА. Платформа читает `events.jsonl`, не stderr.
|
||
⇒ **«тревога построена» не значит «тревога видна человеку»**: для пользователя беты её сегодня нет. Провод
|
||
не строился — это шов и чужая зона.
|
||
|
||
### Д-4 — ЦЕНА СХЛОПЫВАНИЯ, названная сознательно
|
||
|
||
`parseNavDoc` СПЛЮЩИВАЕТ nav: каждая `<a href>` любой глубины — граница (§2.3). Схлопнутые цели понижают
|
||
книгу до `delimited` (диспозиция 6). Вместе это значит: **любой EPUB, где внутри документа главы есть якоря
|
||
на разделы — частая форма хорошо собранной книги, — уходит целиком в `delimited`**, то есть в продажу по
|
||
знакам, а не по главам.
|
||
|
||
На трёх EPUB владельца `collapsed = 0`, поэтому не наблюдалось. Иерархия, по которой можно было бы отличить
|
||
«глава» от «раздел внутри главы», в nav ЕСТЬ и отбрасывается сплющиванием сознательно — значит будущий пак
|
||
может её использовать, не ломая словарь.
|
||
|
||
### Мелкое — исправлено, а не оговорено
|
||
|
||
`chapterTitleRaw` сканировал главу и на `delimited`-разрезе: единственная шапкообразная строка в прозе (одно
|
||
попадание, пол ≥2 не пройден) становилась `title_raw`. Теперь заголовок извлекается **только когда шапки
|
||
победили** — на `delimited` границы провёл разрыв страницы, и честный ответ `""`, а не уверенный неверный.
|
||
|
||
---
|
||
|
||
## 10. ПЕРЕ-ЗАМЕР ТРЁХ EPUB ВЛАДЕЛЬЦА ПОСЛЕ ВСЕХ ДОФИКСОВ
|
||
|
||
По норме, купленной этой же сменой: **экземпляр, которым дефект предъявлен, обязан быть пере-мерен ПОСЛЕ
|
||
починки.** Замер «до» доказывает, что дефект был, и ничего не говорит о том, что стало. Ниже — «после».
|
||
|
||
| книга | глав | structure | ИНВАРИАНТ | attached | excluded | roles-inside-doc | collapsed | toc-unresolved | рун в главах | рун исключено | title_raw |
|
||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||
| Kristoff (en) | 71 | declared | **СОШЁЛСЯ** | 54 | 1 | **1** | 0 | 0 | 1 626 362 | 0 | 71/71 |
|
||
| isekai (ja) | 44 | declared | **СОШЁЛСЯ** | 0 | 1 | 0 | 0 | 0 | 239 209 | 1659 | 44/44 |
|
||
| fifty_shades (en) | 32 | declared | **СОШЁЛСЯ** | 0 | 2 | 0 | 0 | 0 | 879 053 | 976 | 32/32 |
|
||
|
||
Инвариант проверяется ВНУТРИ ингеста и возвращает громкую ошибку — успешное чтение и ЕСТЬ утверждение, что
|
||
он сошёлся. Статистика глав (min/медиана/max, число коротких) совпала с замером «до» по всем трём книгам:
|
||
правки дофикса разрез не двигали.
|
||
|
||
### Именованный перечень исключений — и поправка к моему же отчёту
|
||
|
||
Перечень вместо числа сразу окупился: он показал, что **`excluded 1` у Kristoff — это НЕ документ оглавления.**
|
||
|
||
```
|
||
Kristoff: исключён titlepage.xhtml роль=cover рун=0
|
||
isekai: исключён EPUB/nav.xhtml роль=nav рун=1659
|
||
fifty_shades: исключён …_epub_cvi_r1.htm роль=cover рун=0
|
||
исключён …_epub_toc_r1.htm роль=toc рун=976
|
||
```
|
||
|
||
⛔ **В разделе 8 я связал у Kristoff `service-role-inside-doc 1` и `excluded 1` так, будто это одна сделка.
|
||
Это неверно, и перечень это вскрыл:** исключён пустой титульный лист (0 рун, потери нет), а документ с
|
||
ролью `toc` на фрагменте — `index_split_124.html` — НЕ исключён и лежит внутри главы. Число «исключено: 1»
|
||
позволяло прочитать это неправильно; имя и размер — нет.
|
||
|
||
### Цена Д-2 — теперь ЗАМЕРЕНА, а не предположена
|
||
|
||
```
|
||
ДОКУМЕНТ С guide-РОЛЬЮ toc НА ФРАГМЕНТЕ: index_split_124.html
|
||
рун = 2187
|
||
первые 120 знаков: "Table of Contents\n\n\n\nBOOK ONE\n\n\n\nBOOK TWO\n\n\n\nBOOK THREE\n\n\n\nBOOK FOUR…"
|
||
```
|
||
|
||
⇒ **2187 рун листинга оглавления уедут модели, будут оплачены и покажутся читателю внутри главы** книги
|
||
Kristoff. Это цена дофикса M-1 на этой книге, и она названа числом. До дофикса этих 2187 рун не было — но не
|
||
было и гарантии, что на следующей книге в том же месте не окажется глава.
|
||
|
||
---
|
||
|
||
## 11. ФИНАЛЬНЫЙ ПРОГОН — И ФОРМА, В КОТОРОЙ ОН СДЕЛАН
|
||
|
||
⚠ **`make battery` одной командой НЕ прошла: система дважды убила её по нехватке памяти** (хост держит
|
||
несколько сессий, своп занят на 3.3 ГБ; первый раз убита на тестах, второй — на линтере). Прогон сделан ПО
|
||
СТАДИЯМ, и отчитываюсь именно этими командами, а не «battery зелёная»:
|
||
|
||
```
|
||
go build ./... && go vet ./... && go vet -tags live ./...
|
||
go build -o bin/tmvet ./cmd/tmvet && go vet -vettool=bin/tmvet ./... && go vet -tags live -vettool=bin/tmvet ./...
|
||
GOGC=50 golangci-lint run --timeout=20m --concurrency=2 ./... → 0 issues
|
||
go test <16 лёгких пакетов> -race -count=1 -timeout=20m → ok 16
|
||
go test ./internal/store -race -count=1 → ok 29.5s
|
||
go test ./internal/archguard -race -count=1 → ok 52.2s
|
||
go test ./internal/pipeline -race -count=1 → ok 282.7s
|
||
go test ./... -count=1 -v → EXIT 0, ok 19, FAIL 0
|
||
```
|
||
|
||
Покрытие то же, что у `make battery`: те же цели, тот же `-race`, тот же сбор скипов. Разница только в том,
|
||
что стадии не складывают пик памяти. **Скипов 3**, поимённо те же, что и в базовой линии:
|
||
`TestMinerFullBookParity` · `TestHelperEventsRun` · `TestHelperKillLoop`. **Новых нет.**
|
||
|
||
### Регресс, пойманный ПЕРЕ-ЗАМЕРОМ — той самой нормой
|
||
|
||
Сужение `title_raw` до «только когда шапки победили» оказалось **слишком широким**, и я это поймал не
|
||
рассуждением, а пере-снятием пинов: у односоставного исходника `ch1` заголовок исчез (`заполнено 0/1`).
|
||
Причина: одна глава с одной шапкой не проходит пол ≥2, свидетель у неё `delimited`, и правило выбрасывало
|
||
её собственный настоящий заголовок.
|
||
|
||
**Правило разделено по половинам, которые тянут в разные стороны:**
|
||
|
||
| разрез | где ищется шапка | почему |
|
||
|---|---|---|
|
||
| шапки победили (`detected`) | вся глава | замороженное правило преамбулы прячет шапку главы 1 в её середине |
|
||
| любой другой | только ПЕРВАЯ строка | шапкообразная строка в прозе — одиночное попадание, не граница; но односоставная книга свой настоящий заголовок сохраняет |
|
||
|
||
Пин — `TestTitleRawIsTakenWhereTheCutAllowsIt`, три случая. После правки пины сошлись с базой полностью:
|
||
разрез идентичен и `title_raw` идентичен на всех восьми исходниках.
|
||
|
||
⚠ **Это второй раз за смену, когда правку ловил пере-замер, а не тест.** Первый — Д-2 (не пере-мерил книгу
|
||
владельца после починки). Норма работает ровно потому, что она про ЭКЗЕМПЛЯР, а не про мнение.
|
||
|