diff --git a/docs/research/27-chapter-detection.md b/docs/research/27-chapter-detection.md index c3cc34f1..2ed7b813 100644 --- a/docs/research/27-chapter-detection.md +++ b/docs/research/27-chapter-detection.md @@ -54,4 +54,29 @@ **Расхождения — только в глубине**, не в направлении: Opus типизировал провалы (F1–F7, отдельно «гибрид: половина книги размечена» и «nav и NCX согласны, но оба неверны — agreement ≠ правота»), добавил детект бойлерплейта скрейпов и хрупкость content-hash id на шаблонных шапках; Fable — batch-LLM-ассист и пользовательский редактор структуры детальнее. НИ ОДИН не предложил in-band-разметку глав моделью в потоке перевода (идея владельца 09.08): оба заменили «модель-сейфгард» бесплатным кросс-аудитом. Синтез оркестратора: in-band-разметка остаётся ТРЕТЬИМ эшелоном для книг, где кандидатов нет вовсе (заголовков стилистически нет — оба дизайна честно отправляют такие в ручной редактор), и каналом переведённых названий; её выход обязан проходить тот же валидатор, что и статические гипотезы, и не двигать замороженную нарезку (вид-плоскость). -**Интеграционный вывод для текущего движка** (вопрос владельца «код полагался на ранние точные границы»): оба дизайна требуют ровно того разделения, что названо в диалоге 09.08 — плоскость нарезки/оплаты (заморожена, ключи по контенту сегментов; сегодня это чанки+чекпойнты — уже близко) отделяется от плоскости структуры (версионируемый артефакт банковского типа со своим снапшотом, ревизиями, миграционными картами); полное принятие ревизии структуры в нарезку — только через существующий `--resnapshot`. Главные точки расклейки текущего кода: редакторские юниты группируются в пределах главы (граница формирует платные запросы) · id главы = хеш всего текста главы · банковские since/until по номерам глав. Это масштаб крупного пака; дизайн-ратификация — отдельным решением владельца. +**Интеграционный вывод для текущего движка** (вопрос владельца «код полагался на ранние точные границы»): оба дизайна требуют ровно того разделения, что названо в диалоге 09.08 — плоскость нарезки/оплаты (заморожена, ключи по контенту сегментов; сегодня это чанки+чекпойнты — уже близко) отделяется от плоскости структуры (версионируемый артефакт банковского типа со своим снапшотом, ревизиями, миграционными картами); полное принятие ревизии структуры в нарезку — только через существующий `--resnapshot`. Главные точки расклейки текущего кода: редакторские юниты группируются в пределах главы (граница формирует платные запросы) · id главы = хеш всего текста главы · банковские since/until по номерам глав. Это масштаб крупного пака; дизайн-ратификация — отдельным решением владельца. Детальная карта — §6. + +## §6. Рекомендации реализации, обстучанные о боевой код (два картографа по рабочему дереву, 09.08; все file:line — рабочее дерево с незакоммиченным паком блокеров) + +### Жёсткие правды кода, которые дизайн обязан уважать + +1. **Деньги сегодня ключуются ПОЗИЦИЯМИ, не контентом.** RequestHash несёт плотный номер главы и ChunkIdx (render.go:292-317), chunk_status ищется строго по (book, chapter, chunk_idx, stage) (migrate.go:125, stagerun.go:84); контент-хеш `msgsContentHash` существует, но только как guard резюме, не как ключ поиска. Сдвиг ОДНОЙ границы главы перенумеровывает хвост книги и промахивает все его чекпойнты — перекупка хвоста при неизменных байтах. +2. **Любой новый детектор границ = bump chunkerVersion → движение обоих волновых снапшотов → moveOther → $0-репин НЕ работает вовсе** (repin.go:47-91): перекупаются обе волны на 100%. Это признанный отложенный долг D15.2 — snapshot.go:53-64 прямо: «content-addressed reuse … must be designed». Отсюда несущее следствие: **итерации детекта до развязки денег стоят полный прогон книги; единственное дешёвое окно — пока платная книга одна (стенд)**. +3. **Нарезка сама зависит от глав**: паковка параграфов и edit-юниты рестартуют на границе главы (chunker.go:120-145, 330-397), инжекция банка зависит от номера главы (спойлер-окна since/until в плотных ординалах — memory.go:642-650, sticky-reset на границе — wave.go:82-95). «Структура = вид» недостижима одной перекраской ключей — нужна развязка чанкера от глав, а это само по себе одноразовый resnapshot. +4. **Дыра Р6 (найдена картографом)**: ревизия структуры, не двигающая снапшот (ручной редактор, структура-как-данные), сегодня перекупила бы хвост МОЛЧА — projectRebill пропускает строки с совпавшим снапшотом, контент/позиционная ось невидима консент-гейту (rebill.go:32-35, 144-146). Закрывается дёшево read-path-расширением projectRebill — и это ровно миграционный отчёт §5 п.8. +5. **Паттерн-паки структуры НЕЛЬЗЯ класть в существующие планы данных**: pack.Version() и EmbeddedVersion фолдятся в волновой снапшот (snapshot.go:441-442) — файл паттернов в langpack инвалидировал бы переводческие снапшоты пары, в embedded — всех книг. Нужен ТРЕТИЙ версионный план (structure-pack), фолдящийся только в manifestKey (тот пересобирается за $0). Ловушка рядом: само ДОБАВЛЕНИЕ нового поля в снапшот ломает репин всех старых строк (unknown field = moveOther, repin.go:70-84) — одноразовая цена, планировать в то же окно. +6. **Параграф-уровня в движке нет нигде** (параграфы транзиентны, chunker.go:458-466), и параграф легально пересекает чанки (oversized режется по предложениям) — «якоря на хешах абзацев» из §5 п.9 — целиком новая плоскость; ближний эквивалент сегодня — edit-юнит (ПТ-21) + chapter-ID манифеста. + +### Что переиспользуется (строить НЕ надо) + +Манифест-сайдкар пака блокеров = **~60% механики structure.json**: версионируемый документ с ключом годности и cutTag, стейл-детект с фолбэком «никогда не неправильный ответ», атомарная запись (artifact.go), $0-команда tmctl manifest, шов read-моделей readModelChunks → status/export. Расширение содержимого двигает ТОЛЬКО файл (в снапшот не входит). · projectRebill/checkRebillConsent = готовый механизм отчёта «что перекупится и почём» ДО применения. · matchHeaderLine + parseSectionNumeral + HeadingRule = готовая грамматика `{marker}{numeral}{unit}[sep]{name}` с CJK-читателем числительных. · splitTextChapters = готовый ПЕРВЫЙ паттерн-пак индуктора (частотное доминирование ≥2 + precision-гарды). · Лестница кодировок, zip/OPF/xhtml-транспорт epub, ruby-захват — как есть. · ApplyHeading = $0-канал показа названий (не в чекпойнтах). · Терминологист-батчи на синтетической оси + InternalCall = прецедент для батч-перевода названий. · WaveSignatureStop (exit 3) = прецедент «неоднозначность — громкий исход с выбором пользователя». · Контракт 14 и платформа УЖЕ готовы: Chapter.id opaque, number «не ключ», heading nullable «из данных книги», эпоха курсора = generation of the manifest, resync_required; таблица chapters платформы под opaque id построена (00002_readmodel.sql:91-106). + +### Этапность (рекомендация оркестратора; ратификация — владелец) + +- **Этап 0 — $0, wire-нейтрально, можно немедленно:** titleRaw в манифест (subtitle уже добывается в matchHeaderLine и выбрасывается — только пронести) + provenance у heading (rendered/data) + тип единицы (chapter/fragment) + вердикт структуры в документ; бамп manifestVersion. Закрытие дыры Р6 в projectRebill. Слой пользовательских правок — ОТДЕЛЬНЫЙ сайдкар (в манифест нельзя: он unconditionally перестраивается, hand-edit=invalid по конструкции — manifest.go:36-38, 456-459). +- **Этап 1 — «большой перекрой», единственное resnapshot-окно, пока платная книга одна:** IR-слой + формато-адаптеры (epub nav/NCX + фильтры linear/properties — сейчас opfPackage их вообще не парсит, ingest.go:438-447; fb2) + индуктор с валидаторами и вердиктами + третий версионный план паттернов; развязка чанкера от глав (паковка по сегментам, главы = группировка-вид); спойлер-окна банка с ординалов на chapter-ID (bank-only move — репиннимый класс); контент-адресуемый resume (долг D15.2) — чтобы БУДУЩИЕ ревизии структуры перестали стоить хвост книги; смена формулы Chapter.ID на «окно» (§5 п.9) — до материализации глав у пользователей; новая стадия title (батч-перевод названий) — аддитивный фолд стадии двигает снапшот, вписать в это же окно. Всё — одним согласованным resnapshot стенд-книги с консент-отчётом. +- **Этап 2 — после:** in-band метки глав в потоке перевода (третий эшелон, промпт-сдвиг волны) + канон-гейт названий ($0) + миграционные карты якорей (same/moved/split/merged/gone/new) + корпус в CI. + +### Граница доверия LLM ↔ статика (ответ на наброс владельца 09.08) + +Языковые данные руками не пишутся никогда: сид — проверенные списки зрелых инструментов (zh/ja/en/de готовы); на новом языке/книге с низкой уверенностью универсальных сигналов — один дешёвый LLM-вызов ВЫВОДИТ шаблон книги (распознавание — сила LLM), шаблон проходит тот же статический валидатор, замораживается в снапшот, накопленное абсорбируется в паттерн-пак. LLM — генератор и арбитр данных; статика — исполнитель, валидатор (сохранение символов, монотонность, коридоры — валидировать LLM-выход может только она) и масштабатор. Полный LLM-проход по книге отвергнут (§4); in-band-разметка в потоке перевода — третий эшелон для книг без кандидатов и канал переведённых названий.