diff --git a/docs/BACKEND_BOOKWRITER_PACK_SESSION_PROMPT.md b/docs/BACKEND_BOOKWRITER_PACK_SESSION_PROMPT.md
index 6be1709a..e3b7310d 100644
--- a/docs/BACKEND_BOOKWRITER_PACK_SESSION_PROMPT.md
+++ b/docs/BACKEND_BOOKWRITER_PACK_SESSION_PROMPT.md
@@ -4,13 +4,15 @@
Проект переводит книги и **не умеет отдать книгу**. Ни одной стороной. Движок отдаёт только поток:
`renderExport` (`backend/cmd/tmctl/render.go:565`) печатает стабильный JSON `tm-export-v1` либо
-`--plaintext` с аудит-баннерами `=== CHAPTER N CHUNK M ===`. Греп `epub|fb2|docx` по `backend/**` минус тесты даёт **43** попадания, из них 29 в `internal/chunk/`
-`ingest.go`. ⚠ **И писатель EPUB в дереве УЖЕ ЕСТЬ** — `backend/internal/chunk/chunktest/epub.go:37-112`
+`--plaintext` с аудит-баннерами `=== CHAPTER N CHUNK M ===`. Греп `epub|fb2|docx` по `backend/**/*.go` минус
+`_test.go` даёт **43** строки, из них 26 в `internal/chunk/ingest.go` — всё на стороне ЧТЕНИЯ.
+⚠ **И писатель EPUB в дереве УЖЕ ЕСТЬ** — `backend/internal/chunk/chunktest/epub.go:37-112`
(`BuildEPUB`/`BuildEPUBAt`): настоящий OCF-контейнер, `mimetype` первым и `zip.Store`, `container.xml`,
-`content.opf` с manifest+spine, XHTML. Он ТЕСТОВЫЙ, но обратим ингесту по построению и запинен
-(`ingest_test.go:537` `TestEPUBFixtureMimetypeIsOCFConformant`). Его собственная шапка предупреждает:
-дублирование zip/OPF-строителя развело бы две копии. **Начинать с него, а не с нуля.**
-Платформа обещанные каноном ручки выдачи не монтирует, и её `export_formats` захардкожен пустым.
+`content.opf` с manifest+spine, XHTML. Он ТЕСТОВЫЙ, обратим ингесту по построению и запинен
+(`ingest_test.go:537` `TestEPUBFixtureMimetypeIsOCFConformant`). Его шапка предупреждает: дублирование
+zip/OPF-строителя развело бы две копии. **Начинать с него — как именно, размечено в §3.1.**
+Платформа обещанные каноном ручки выдачи не монтирует, её `ExportFormats` захардкожен пустым
+(`platform/cmd/tmplatformd/runner.go:191`).
**Что решит твой результат:** переведённую книгу можно будет открыть в читалке. Сегодня цель №1 канона
(«издательское художественное качество») не проверяется ничем на выходе: результат некуда открыть.
@@ -22,47 +24,113 @@
## 1. Зона записи и git
Пишешь только в `backend/`. **Не коммитишь**: дерево готовишь и передаёшь оркестратору. Канон git — в
-`CLAUDE.md`, здесь не дублируется. Чужие незакоммиченные файлы не трогать.
+`CLAUDE.md`, здесь не дублируется.
## 2. Карта чтения — ПЯТЬ позиций, дальше только по их ссылкам
-1. `backend/internal/pipeline/export.go` — **прежде всего блок CAVEATS над `Export`**: там названы три
- состояния, которые твой файл обязан не соврать (дрейф конфига, ghost-строки, pending-юниты).
-2. `backend/cmd/tmctl/render.go:562-630` — существующий рендер и то, чем `--plaintext` НЕ годится
- в качестве книги (аудит-баннеры, флаги в скобках).
+1. `backend/internal/pipeline/export.go` — **прежде всего блок CAVEATS над `Export`**: три состояния
+ КНИГИ, о которых файл обязан не соврать. ⚠ Ещё два состояния — ПОЮНИТНЫЕ, в CAVEATS их нет; они в §3.2.
+2. `backend/cmd/tmctl/render.go:562-630` — существующий рендер: чем `--plaintext` НЕ годится в качестве
+ книги (аудит-баннеры, флаги в скобках) и арифметика `withheld`/`incomplete` (`:573-587`), которую
+ твой файл обязан повторить.
3. `backend/internal/chunk/ingest.go` — как книга РАЗБИРАЕТСЯ из epub: писатель обязан быть обратим к
- этому чтению, иначе круг «загрузил → получил» не замыкается.
+ этому чтению, иначе круг «загрузил → получил» не замыкается (§4 — равенством, не парсом).
4. `docs/architecture/12-go-style-notes.md` §0 — норматив общности: **пары языков это ДАННЫЕ**.
-5. `docs/PROGRESS.md` строка **201** (синтетический заголовок едет внутри текста юнита) — твоя развилка,
- см. §3.3.
+5. `docs/PROGRESS.md`, строка БЭКЛОГА **201** (`grep -n '^| 201 |'`) — синтетический заголовок едет
+ внутри текста юнита; твоя граница, см. §3.3.
## 3. Работа
### 3.1 Что построить (делай РОВНО так)
**Движковый писатель книги из `BookExport`**, вызываемый из `tmctl`. Минимум два формата: **EPUB**
-(книга для читалки) и **чистый текст** — без аудит-баннеров, без флагов в скобках, без служебных
-маркеров. Существующий `--plaintext` не трогать и не переименовывать. ⚠ Обоснование именно такое, а не обратное:
-`eval/README.md:80` говорит «`--plaintext` для глаз, дефолтный JSON для скриптов» — то есть машинная
-поверхность это JSON, и у него ДВА потребителя: полигон и **платформа** (`platform/internal/runner/`
-`engine.go:137` → `ingest/export.go` `DecodeExport`; `STACK_DECISIONS.md:376` называет его «единственным
-каналом, несущим ТЕКСТ пары»). Менять форму `tm-export-v1` — значит менять платформу из чужой зоны.
+(книга для читалки) и **чистый текст** — без аудит-баннеров `=== CHAPTER …`, без флагов в скобках и без
+операторских маркеров вроде `gapMarker` (`render.go:56-67` — английская лексика аудит-поверхности,
+читателю не годится). ⚠ Это запрет на ОПЕРАТОРСКИЙ словарь, не на честность: как показать читателю
+неполноту — решение §3.2. Существующий `--plaintext` не трогать и не переименовывать — это его
+ВЫВОД, запинённый `export_cli_test.go`/`exportgap_cli_test.go`; вынос арифметики withheld/incomplete в
+общий хелпер допустим. JSON — машинная поверхность двух потребителей: полигон (`eval/README.md:80` —
+«`--plaintext` для глаз, дефолтный JSON для скриптов») и платформа (`platform/internal/runner/engine.go:148`
+`ExportArgs` → `:191` `ingest.DecodeExport`); менять форму `tm-export-v1` — значит менять платформу из
+чужой зоны.
+
+⛔ **ОДИН файл на книгу на формат** — так требует контракт (`docs/architecture/14-api-contract/openapi.yaml:2489-2492`:
+`Export` = «A built copy of the book, behind a link»); деление на главы живёт ВНУТРИ файла (spine EPUB,
+разделители в txt), а не набором файлов.
+
+**Основа — `chunktest/epub.go` (инварианты — РОВНО так, форму выноса решаешь сам).** Напрямую из
+`tmctl` он невызываем: принимает `*testing.T` и зовёт `t.Fatal`/`t.TempDir()` (`epub.go:37-50`), а
+копировать его второй раз запрещает его же шапка. Значит общую OCF-часть (`mimetype` первым и
+`zip.Store`, `container.xml`, запись entry) выносишь в прод-пакет без `import "testing"` и переводишь
+`chunktest` на неё — либо пишешь прод-писатель от его инвариантов и аргументируешь, почему две копии не
+разойдутся. Инварианты: `import "testing"` в прод-коде запрещён · тест-файлы, импортирующие `chunktest`
+(их 9), остаются зелёными БЕЗ правки ассертов и сигнатур · в прод НЕ переезжает ничего тестового:
+ручки `MType`/`EntryName`, OPF-заглушка с `
`); `
` абзаца не даёт (`blockTags`,
+ `ingest.go:614`); побайтово текст не совпадёт — сравнивай абзацы. `err == nil` сам по себе не
+ доказывает ничего: вся книга одним документом «разбирается» одной главой.
+- **Детерминизм:** два прогона в двух ПРОЦЕССАХ → `cmp` побайтно. Покажи команду и результат.
+- **Пять состояний §3.2 — по посадке на каждое:** pending (coldrun-a или синтетика) · ghost, drift,
+ withheld, incomplete (все четыре есть на acceptance) · чистая полная книга — только синтетика. Две
+ посадки УЖЕ есть — не изобретай: `backend/internal/pipeline/export_test.go` `TestExportManifestPending`
+ (`:186`) и `TestExportDetectsConfigDrift` (`:229`) на хелперах из `runner_test.go`. ⚠ Ghost-посадки в
+ тестах НЕТ (греп `GhostRows` по `*_test.go` пуст) — строишь сам, отдельной строкой в отчёте.
+ Golden-харнесс (`golden_test.go`) — гард wire пайплайна, не инструмент для твоего файла;
+ `TM_UPDATE_GOLDEN` не трогать. Каждое состояние обязано отработать так, как ты решил, и это видно в
+ файле или в отказе. Файл реальной книги, её копии и cmp-вывод — рабочее временное в песочнице: в
+ отчёт идут команды и вывод; воспроизводимость на приёмке держит герметичный тест писателя в
+ `backend/` на синтетической книге (`chunktest.BuildEPUB`, образец герметичного `Export` — `export_test.go`).
- ⛔ **Править или удалять тест ради зелени НЕДОПУСТИМО (D39.121).** Это не абстракция: любой вариант,
трогающий заголовок, красит `backend/internal/chunk/chunker_heading_test.go:121`, и самый дешёвый
способ вернуть зелень — правка ассерта. Несогласие с тестом — пинг, а не правка.
@@ -139,32 +324,38 @@ EPUB — это zip, и в нём по умолчанию лежат時metки
- **Разрешаю субагентов** (иначе дефолт харнесса тихо запретит). Модель задавай ЯВНО и знай, сколько
их работает. Трудный вопрос — старшей модели.
- ⚠ **Адверсариальный проход по своей готовой работе перед сдачей.** Направление: файл, молча
- притворяющийся полным; недетерминизм zip; заголовок, потерянный или задвоенный; кодировка и
+ притворяющийся полным — в том числе при `pending_units=0` (пустая глава, дыра внутри юнита);
+ `dcterms:modified` из часов; заголовок потерянный, задвоенный или пустой; nav в spine; кодировка и
направление письма; книга из одного юнита и книга из нуля юнитов. **Пока идёт проход — дерево не
двигается:** либо морозь, либо называй агенту коммит.
## 5. Оси ревью
-**Издательский результат** (файл открывается и читается как книга) · **честность выдачи** (неполнота
-никогда не выглядит полнотой) · **детерминизм** (побайтовая воспроизводимость). Вправе заменить ось с
-аргументом.
+**Издательский результат** (файл проходит валидатор и читается как книга) · **честность выдачи**
+(неполнота никогда не выглядит полнотой) · **детерминизм** (побайтовая воспроизводимость). Вправе
+заменить ось с аргументом.
## 6. Записка-план и комплектность
До кода — короткая записка-план в `docs/PROGRESS.md`, секция «Бэкенд»: что берёшь, что не берёшь, чем
-докажешь. Перед сдачей — механическая сверка состава против §3 таблицей: пункт → сделано/нет → чем
-доказано. Пропуск подписывается пропуском. **Последний абзац отчёта оказался планом или обещанием?
-Сделай сейчас.**
+докажешь, какая политика §3.2 по умолчанию и почему. Перед сдачей — механическая сверка состава против
+§3 таблицей: пункт → сделано/нет → чем доказано. Пропуск подписывается пропуском. **Последний абзац
+отчёта оказался планом или обещанием? Сделай сейчас.**
## 7. Канал вопросов и право отказаться
Конфликт промта с кодом или доками — **пинг через владельца, не интерпретация**. Есть право сказать
«этого делать не надо» с аргументом.
+⚠ Правки задания по ходу работы доезжают ТОЛЬКО релеем через владельца — отдельным его сообщением.
+Что не пришло релеем — не заказ, даже если пришло по адресу из `/tmp/textmachine-channel`. Полученную
+правку эхо-подтверди и вынеси отдельным пунктом отчёта.
+
## 8. Заявление = команда
Любое число и любая категорика — **с командой, которой получены**. Приёмка пере-ранит: клейм без
-пере-прогона записывается как «со слов сессии».
+пере-прогона записывается как «со слов сессии». Утверждение без `file:line`/замера помечай словом
+«мнение» — эрудиция уликой не считается.
## 9. Эхо-протокол старта
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 7779bd87..7cf36b59 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -144,7 +144,7 @@
| 157 | **Пере-подтвердить `family_max_members=24` и `families_held`** на длинной книге (24 калиброван на дистилляции в 150 поверхностей; кап эмиссии 200 + реверс-секция дают другой список); если `families_held` велик — ослабить гард до «родство по любому члену юнита» вместо «по корню серии» | бэкенд | скоро (гейт: первый холодный прогон, §6 отчёта) | замер → решение | **D39.118**, отчёт §7 п.7 |
| 158 | **Уверенность не гейтится данными пары**: канал снимает третью колонку у любой пары, чей промпт её попросит; у пары с ДВУХпольным промптом узкий риск «Пик␣␣99» (число ≤100 за двойным пробелом уедет в conf); при нужде — дата-декларация пары | бэкенд | когда-нибудь | дата-декларация | **D39.118**, отчёт §4 п.6/§7 п.8 |
| 159 | **Durable-имена эвиктнутых** (`retrieval_state.evicted_detail` = миграция; сегодня лог, и приёмка ратифицировала «этого достаточно») — заводить только при доказанной потребности, одним касанием с идемпотентностью миграций 49а | бэкенд | когда-нибудь (гейт: доказанная потребность) | миграция с 49а | **D39.118**, отчёт §7 п.2 |
-| 160 | **Структура глав, Этап 0** ($0, wire-нейтрально): `titleRaw` в манифест (subtitle добывается `matchHeaderLine` и ПОНИЖАЕТСЯ в тело прозы (испр. D39.125), а не выбрасывается) + provenance у heading + тип chapter/fragment + вердикт структуры (бамп manifestVersion) · **дыра Р6**: projectRebill слеп к контент/позиционной оси (rebill.go:32-35) — ревизия структуры перекупила бы хвост МОЛЧА, закрыть read-path-расширением · слой пользовательских правок отдельным сайдкаром · **зонная половинка темы:** Ф-30 фронта / В-4 владельца (глава без заголовка на экране — метка из ДАННЫХ, К-3) ждут ровно эту строку; `heading` манифеста до неё = ВРЕМЕННЫЙ рендер движка (D39.122 п.2д) | бэкенд | скоро (ГЕЙТ ПАЛ словом владельца 15.08, D39.136 п.3: ставится ближайшим бэкенд-паком с мандатом максимального качества; тесты структуры глав — узлом будущего холодного прогона) | пак | research/27 §6 Этап 0, D39.122 п.4, D39.136 |
+| 160 | **Структура глав, Этап 0** ($0, wire-нейтрально): `titleRaw` в манифест (subtitle добывается `matchHeaderLine` и ПОНИЖАЕТСЯ в тело прозы (испр. D39.125), а не выбрасывается) + provenance у heading + тип chapter/fragment + вердикт структуры (бамп manifestVersion) · **дыра Р6**: projectRebill слеп к контент/позиционной оси (rebill.go:32-35) — ревизия структуры перекупила бы хвост МОЛЧА, закрыть read-path-расширением · слой пользовательских правок отдельным сайдкаром · **зонная половинка темы:** Ф-30 фронта / В-4 владельца (глава без заголовка на экране — метка из ДАННЫХ, К-3) ждут ровно эту строку; `heading` манифеста до неё = ВРЕМЕННЫЙ рендер движка (D39.122 п.2д) | бэкенд | скоро (ГЕЙТ ПАЛ словом владельца 15.08, D39.136 п.3: ставится ближайшим бэкенд-паком с мандатом максимального качества; ⚠ 30.08: впереди неё стоит блокер 236 — писатель книги, очередь №20 п.1, «ближайшим» читать как «следующим после него»; тесты структуры глав — узлом будущего холодного прогона) | пак | research/27 §6 Этап 0, D39.122 п.4, D39.136 |
| 161 | **Структура глав, Этап 1 «большой перекрой»** (единственное дешёвое resnapshot-окно — пока платная книга одна, ДО интейка): IR + формато-адаптеры (epub nav/NCX, fb2) + индуктор с валидаторами и вердиктами + третий версионный план паттернов (не в снапшот волн) + развязка чанкера от глав + банк-окна на chapter-ID + контент-адресуемый resume (долг D15.2) + формула Chapter.ID «окно» + роль title (мини-сессия названий: редакторский слот · инъекция банка · двухфазно вокруг подписи) + аддитивное расширение контракта 14; жизненный цикл дерева — research/27 §3а (решение владельца 09.08) | бэкенд (+фронт/платформа аддитивно) | скоро (постановка санкционирована D39.136 п.3: СНАЧАЛА дизайн-пак с мандатом максимального качества, стройка после ратификации дизайна; ⚠ дешёвое окно закрывается первой книгой внешнего пользователя) | дизайн-пак → ратификация → пак(и) | research/27 §3/§5/§6, D39.136 |
| 162 | **Структура глав, Этап 2**: in-band метки глав в потоке перевода (третий эшелон детекта + коррекция структуры по ходу черновой волны, §3а п.3) + канон-гейт названий ($0) + миграционные карты якорей (same/moved/split/merged/gone/new) + калибровочный корпус детекта в CI | бэкенд + полигон | когда-нибудь (после Этапа 1) | пак + корпус | research/27 §5/§6 Этап 2 |
| 163 | **Версии модулей x/text|x/net двигают нарезку МИМО всех ключей** (NFC в NormalizeSource — таблицы модуля, не тулчейна; epub-токенайзер): бамп `go get -u` тихо меняет нарезку при свежих на вид манифесте/чекпойнтах/снапшоте — класс всей чекпойнт-плоскости, унаследован манифестом; вплести версии модулей (debug.ReadBuildInfo) в NormVersion/chunkerVersion либо явный запрет бампа без `--resnapshot` | бэкенд | когда-нибудь (до второго прогона книги) | малое касание | **D39.122**, приёмка (охотник №1) |
@@ -176,7 +176,7 @@
| 196 | **Остаточный класс строки 176: неперехватываемые `fatal error` Go** (конкурентная запись в map, OOM, стек-оверфлоу) по-прежнему дают exit 2, то есть читаются платформой как «завершено с флагами» — `recover` их не ловит по устройству языка (подтверждено линзой шва исполнением). Строка 176 закрыта для ПАНИК, не для fatal. Лечение — вне exit-контракта: сторож процесса у платформы либо маркер живости в артефакте прогона | бэкенд/платформа | когда-нибудь | отдельное решение | приёмка №18, линза шва |
| 197 | **Фикс-лист приёмки пака честности ФЧ-1…ФЧ-8** (D39.149; тело — [archive/PROGRESS-2026-08-17.md](archive/PROGRESS-2026-08-17.md), запись приёмки №18): три НЕПРИБИТЫЕ гарантии (снятие фильтра по книге в `CheckpointUsageForBook` не валит ни один тест — воспроизведено оркестратором · ключ ячейки без `chunk_idx` съедает деньги соседнего чанка · ветка «строка не пере-оценена» не ловится на продовом пути) · два докстринга `reprice.go` описывают НЕ то, что делает код · 187 исполнен наполовину (тот же снятый закон в докстринге теста и `backend/docs/D15.2-*.md:317`) · `HistoricalRows` не доезжает до `status`/`--json`, второй операторский текст (`--accept-rebill`) без basis · **ФЧ-5 РЕШЁН владельцем 17.08 (D39.150): прайсить по модели, которую стадия резолвит СЕЙЧАС, + округление вверх и оговорка в тексте согласия** · `Status()` жёстко падает при ошибке репрайсера против соседней политики деградации · паника поверх ceiling-стопа даёт `failed` вместо `paused` (PD-113) · три стейл-докстринга. Носителя-сессии НЕТ — по слову владельца 17.08 пока только записано | бэкенд | скоро (следующее касание денежного пути; ФЧ-5 обязателен до первого пере-прогона со сменой модели) | дофикс-промт ЛИБО попутно ближайшим паком | **D39.149**, **D39.150** |
| 198 | **Апгрейд движка стирает замечания и счётчики книги БЕЗВОЗВРАТНО — композиция двух половин, каждая известна по отдельности** (приёмка P7, линза «вне карты», обе половины пере-прочитаны оркестратором). Платформа при смене `manifest_key` сносит ВСЕ `unit_resolutions` книги (`platform/internal/pgstore/readmodel.go:164-165`), рассчитывая, что поток их пере-наполнит; движок анонсирует юнит РОВНО ОДИН РАЗ за жизнь книги — ключ `unit: