# АРХИВ: секция «Бэкенд» журнала прогресса, эра 24–28.08.2026 > ⚠ **ИСТОРИЧЕСКОЕ. Инструкции отсюда НЕ ИСПОЛНЯЮТСЯ.** Это вынесенная 28.08 прожитая проза зонной > секции `docs/PROGRESS.md` — пять итогов бэкенд-сессий и их записки-планы. **ВСЕ описанные здесь паки > ЗАЛЕНДЖЕНЫ**, и каждый «дерево готово, НЕ закоммичено» относится к состоянию, которого больше нет. > > **Куда что уехало:** входная дверь шва (24–27.08, четыре сессии) — `D39.158`, лендинг `d1eb8a9`; > пак «тихая порча» (28.08) — `D39.164`, лендинг `7d0c6f2`. > > ⚠ **Остатки, которые здесь названы, живут СТРОКАМИ единого бэклога, а не этим файлом:** **228** > (алиас возвращает отклонённую поверхность), **229** (снапшот не фолдит модель внутренних гейтов), > **230** (размен сходимости против поучения), **231** (смета пере-прохода), **141** (остаток > детектора сносок), **131** (кап инъекции не считает маркер). Вендор-вердикт по Gemini живёт > ПИНГОМ ПОЛИГОНУ в живом журнале — здесь его копия, не носитель. > > Вынесено аудитом документации 28.08 (D39.167) по слову владельца «пройденную прозу можно сносить». **⟶ Состояние бэкенда — CURRENT-STATE (один носитель, D39.80); здесь только пинги живых сессий.** **✅ ИТОГ БЭКЕНД-СЕССИИ 28.08 — ПАК «ТИХАЯ ПОРЧА» (`docs/BACKEND_SILENT_HARM_SESSION_PROMPT.md`), все три пункта. Дерево готово, НЕ закоммичено.** **§6 КОМПЛЕКТНОСТЬ — пункт → что сделано → каким ИСПОЛНЕНИЕМ подтверждено.** | Заказ | Что сделано | Чем подтверждено ИСПОЛНЕНИЕМ | |---|---|---| | **§3.1** инъекция банка доезжает на каждом провайдере либо громкий отказ | Новая ось `llm.Capability.SystemMessages` (`multi`\|`single`, `capability.go:87-127,158-165`); под `single` `toOpenAIMessages` СКЛЕИВАЕТ ведущий системный ряд в одно сообщение (`httpllm.go:513-559`) и падает громко на системном посреди диалога — зеркально Anthropic; YAML-ось `capabilities.system_messages` с валидацией enum (`config/models.go:101-106,566-578,646-650`); квирк объявлен у провайдера `gemini` с вендор-ссылкой и датой (`configs/models.yaml:120-137`) | 6 тестов `internal/llm/systemmessages_test.go` + сквозной на ОБОИХ волнах `internal/pipeline/systemmessages_wave_test.go`. Мутация «ось игнорируется» → падает `TestSingleSystemEndpointCarriesTheBankOnBothWaves` обеими под-ветками и `…CarriesTheInjection` с текстом `got 2 (roles [system system user])`. Мутация «ось теряется по дороге из YAML» → тот же тест. Каталог: `SH1`, `SH2` | | **§3.2** дыра выдачи видна читателю файла | ⚠ **Заказ пере-формулирован по находке** (разрешено оркестратором 28.08): маркер уже существовал и ВРАЛ. `ChunkOutcome`/`ChunkExport` несут `DroppedMembers` (`bookrun.go:69-75`, `export.go:45-51`, `waverun.go:488`, `export.go:227-230`); `cmd/tmctl/render.go` печатает маркер В ПОТОКЕ ТЕКСТА (`gapMarker`, :26-63) в обоих рендерах и честные счётчики `withheld`/`incomplete` | Сквозной прогон БОЕВОГО бинаря `cmd/tmctl/exportgap_cli_test.go`: `tmctl translate` → `tmctl export --plaintext`, текст ДО и ПОСЛЕ на ОДНОМ состоянии (ниже). Матрица семи состояний. Мутации `SH3` (маркер пуст) и `SH4` (счёт выпавших не ведётся) | | **§3.3** повтор принятого документа сходится | `refuseSeedConflicts` больше не срабатывает, когда отказ УЖЕ на записи (`membank/decisions.go:365-393`, новый `rejectsHoldSurface` :690-700); порядок записи в `writeDecisionFiles` изменён на REJECTS-FIRST с обоснованием (`bankdecisions.go:427-441,458-489`) | 5 тестов `membank/decisions_converge_test.go` + 3 дверных `pipeline/bankdecisions_converge_test.go` (реальные файлы, реальные классы отказа). Мутации `SH5` (сужение снято) и `SH6` (порядок возвращён) | **РЕШЕНИЯ ПО ПРОПУСКАМ И РОДНЕ — строкой каждое, как просил оркестратор.** - **Четвёртая дыра (строка 141) — ВЗЯТА БЕСПЛАТНО, работы не потребовала.** Проверено исполнением (проба по `internal/checks`, файл удалён после замера): сноска `[1] 蛊真人 — название романа.` мимо детектора примечаний идёт, но поднимает класс `CJKLeak`, а он `CosmeticOnly` (`checks/sanitizer.go:161-163`) ⇒ `StripCosmetic` вырезает ханьцзы, юнит уходит `sanitizer_stripped`, и баннер «leak cleaned, verify» для НЕГО правдив. Механизм этого же заказа накрывает класс без правки детектора — граница промта соблюдена буквально. ⚠ Смежное, НЕ взятое и названное строкой: сноска БЕЗ ханьцзы (`[1] Название романа.`) не поднимает ни одного класса (замер: `total=0`), едет как есть — но текста при этом НЕ теряет, это лишняя добавка, а не дыра. Маркер на ней дезинформировал бы (⛔ анти-скоуп промта). Ловля таких сносок — работа В ДЕТЕКТОРЕ, промт её прямо запретил. - **Д1 — НЕ ВЗЯТ, отдельным предметом.** Право §9. Канон определяет decline ПОВЕРХНОСТНО («declining a surface removes EVERY window of that surface», `14-api-contract/openapi.yaml:2051`), и расширять его до энтити значило бы менять ратифицированный контракт из бэкенд-сессии. Вторая половина Д1 («память глушит стоп») — уже НАЗВАННЫЙ и принятый двусторонний остаток в комментарии `mining.go:308-313`, а не необнаруженный дефект. ⚠ Но одна траектория Д1 реальна и требует ЧУЖОГО решения: эмиссия майнера энтити-широка (`miner_emit.go` `clusterTouches`), а фильтр авто-банка ключуется только по собственному src строки (`mining.go:662`), поэтому ОТКЛОНЁННАЯ поверхность может вернуться алиасом уцелевшей строки. Это противоречит канону уже сегодня. **Не чиню в этом паке** (правильная форма — снять алиас со строки, а не снести строку; это предмет банковой онтологии, а не тихой порчи) — **но это заказ, который стоит выдать.** - **Д3 — НЕ ВЗЯТ, вне моей зоны.** `LiftBankStop` в `backend/` не существует ни в каком написании; символ живёт в `platform/internal/runs/spawn.go` и `reconcile.go`, то есть в зоне, запертой паком P9. Движковая половина при этом здорова: порядок «карта → память» ратифицирован и исполнен (`mining.go:191` пишет карту, `:274` метит память), отказ памяти оценён в комментарии `:275-276` как «one extra stop, nothing lost». ⚠ Поправка к моему же раннему пингу: я сказал «гарда не существует» — точнее «не существует В ДВИЖКЕ». - **Сосед 131 — НАЗВАН, не чиню.** Кап инъекции считает только `src → dst` (`membank/memory.go:517-521` `glossaryLineTokens`), а рендер дописывает к КАЖДОЙ строке `tx.UnverifiedMarker` (`:706`) либо `genderConstraintNote(...)` (`:711`), а редакторский блок ещё и префикс `- ` с кавычками-ёлочками (`:821`). То есть отгружаемая строка строго длиннее той, за которую платит бюджет; на коротких именных строках перерасход велик. Тема денег и качества инъекции — не тихий брак в тексте. **НАХОДКИ, МЕНЯЮЩИЕ ПРОМТ (все три подписаны, ни одна не абсорбирована молча).** 1. **§3.2: маркер существовал и ВРАЛ.** Ветка `case ce.FinalText != ""` печатала «(leak cleaned, verify)» ЛЮБОМУ флагнутому юниту с текстом, а c-lite member-drop (`export.go` ставит `DispFlagged` при непустом тексте) попадал ровно в неё. Существующий тест не ловил: покрывал ветку только причиной `sanitizer_stripped` (`export_cli_test.go:66`). Предмет заказа изменён с «завести маркер» на «перестать врать и покрыть непокрытое» — разрешено оркестратором 28.08 при закрытом гейте. 2. **§3.2: подсказка промта про место правки была опасна.** Промт указывал `export.go:237` рядом с `ApplyHeading`. Туда нельзя: `ChunkExport.FinalText` — поле артефактного контракта, а провод судит `translated`/`withheld` предикатом «текст непуст» (`waverun.go:203-204` и `events.go:538-554`, `runevents.go:130` `Shipped`). Маркер в пустом юните перевернул бы `withheld` → `translated` — ровно ⛔(а) промта. `ApplyHeading` этого не делает лишь потому, что на пустом тексте он no-op. Маркер живёт в РЕНДЕР-слое. Проверено: `FlagReason`-константы до и после ИДЕНТИЧНЫ, `exportVersion` не двигался, `dropped_members` аддитивно и `omitempty`. 3. **§3.1: посылка «Gemini принимает ровно одно» отозвана автором** (оркестратор, 28.08) — вендор- вердикт со ссылками и датами отправлен ему отдельно, для `00-provider-quirks.md`. Кратко: структурная посылка ПОДТВЕРЖДЕНА (`systemInstruction` — один `object (Content)`, `Content.role` ∈ {user, model}), поведение шима вендором НЕ документировано нигде, наш замер даёт «не склеиваются» и «инструкция первого не исполнена»; «выживает одно или ноль» остаётся ОТКРЫТЫМ и для решения безразлично. **ДЕФЕКТ, КОТОРЫЙ Я ВНЁС САМ И ЗАКРЫЛ (называю, потому что он показателен).** Первая редакция ветки маркера срабатывала по `DroppedMembers > 0` БЕЗ проверки непустого текста. Юнит, у которого выпали ВСЕ члены, ship-ит пустоту — и получил бы баннер «(INCOMPLETE)» с фразой «фрагмент отсутствует в тексте НИЖЕ», указывающей на несуществующий текст. То есть я едва не заменил одну ложь читателю на другую. Найдено собственным разбором ветвлений до опровергателей, закрыто гардом на обоих сайтах вызова, покрыто состоянием «выпали все» в матрице и запинено мутацией (снятие гарда → тест падает). **§5 ОСИ РЕВЮ.** 1. **Читатель книги.** Все четыре класса проверены на ОТГРУЖЕННОМ ТЕКСТЕ, не на структурах: (а) c-lite — был помечен ложно, теперь помечен верно и в потоке текста; (б) юнит целиком withheld и (в) `glossary_miss` — помечены верно и раньше, работы не требовали, теперь ещё и не попадают под маркер по ошибке; (г) сноска с ханьцзы — накрыта существующим верным маркером. 2. **Пара, которой в репо нет.** Правка не ветвится по паре ни в одном месте: ось `system_messages` — свойство ЭНДПОЙНТА, маркер — английский операторский словарь той же поверхности, что и соседние баннеры, поэтому у книги с любым `target_lang` и БЕЗ langpack вывод байт-идентичен. Специально НЕ стал класть маркер в langpack: `InjectionTextsFor` для незнакомой цели возвращает нулевое значение молча (`lang/embedded.go:411-423`) — маркер бы исчез, и дыра снова стала бы тихой ровно для той пары, ради которой мандат общности и написан. 3. **Детерминизм.** Голден зелёный (`go test ./internal/pipeline/ -run TestGolden -count=1`), и это сильная улика: голден пинит ПОЛНОЕ тело запроса с обоими системными сообщениями (`testdata/golden/capture.golden:110`), то есть глобальная свёртка сделала бы его красным, а квирк-свёртка не трогает никого, кто квирк не объявил. `omitempty` на новом поле запинен тестом (`TestCapabilityWithoutTheAxisMarshalsUnchanged` сверяет БАЙТЫ). Снапшот фолдит Capability только праймари и `escalate_to` (`snapshot.go:316-340`), а в `pipeline-c1.yaml` это `deepseek-v4-pro` (`:60`) — gemini там лишь третий в цепочке при `budget_usd: 0` и SINGLE-HOP раннере ⇒ снапшот боевого конфига не двигается, перекупки нет. **§8 OBSTACLE — что НЕ удалось и что НЕ проверено.** - **Живого вызова к Gemini НЕ было** (пак платных вызовов не требует, и я их не делал). Что склейка ПОМОГАЕТ, доказано структурно вендор-докой и логикой «при любом чтении, кроме склеивания, два системных теряют текст» — но НЕ замерено на проводе. Решающая проба стоит денег и принадлежит полигону: маркер на ПОСЛЕДНЕЕ системное + user-only контроль закрыли бы открытый вопрос «выживает одно или ноль». Также: проба полигона гонялась на `gemini-3.1-flash-lite`, а в цепочке стоит `gemini-3.1-pro-preview` — слой тот же, слаг другой, перенос не замерен. - **Достижимость §3.1 сегодня НУЛЕВАЯ, и это не смягчение дефекта, а его честная рамка.** Раннер исполняет SINGLE-HOP, `escalate_to` боевого конфига — `deepseek-v4-pro`, `budget_usd: 0`. Gemini не достигается ни одним шиппинг-конфигом; дефект ждал первого конфига, который назовёт его в `escalate_to` или в `chain[0]` лейбла. Кривая пост-чек-промахов на эскалационном хопе, которую §4.2 просил как второй замер, СНЯТА БЫТЬ НЕ МОЖЕТ по той же причине — мерить нечего. - **Ловушка, которую я НЕ чинил и обязан назвать: `capabilities` у провайдера с `kind: anthropic` принимается валидацией и молча игнорируется** — `clients.go` не передаёт `Cap` в Anthropic-клиент вовсе. Это ПРЕД-СУЩЕСТВУЮЩИЙ класс (так ведут себя ВСЕ оси: `budget_field`, `temperature`, `reasoning`, `min_max_tokens`), моя ось лишь наследует его. Чинить правильно — тащить `kind` в `validateCapabilities`, что трогает каждую ось; в этом паке это чужой предмет. Практический риск сегодня нулевой: провайдера `anthropic` в `models.yaml` нет. - **Изменён СУЩЕСТВУЮЩИЙ тест — заявляю прямо, не прячу в дифе.** `internal/pipeline/bankreport_test.go` `TestHalfLandedIsNamedPerFile` кодировал ПОРЯДОК переименований в своей фикстуре (занимал путь rejects, чтобы упал второй rename). Порядок изменён заказом §3.3, и фикстура пере-нацелена на путь delta. Утверждение НЕ ослаблено: по-прежнему требуется, что ровно одна половина легла, что это ПРОВЕРЕННЫЕ байты и что отчёт называет какая. Сверх того добавлен `TestTheRenameOrderIsWhatMakesADeclineRecover`, который пинит САМ порядок вместе с его причиной, чтобы его нельзя было вернуть как «уборку». **Если оркестратор считает это подгонкой — я готов откатить и вынести §3.3-половину с полусостоянием отдельным заказом.** - **Каталог мутаций: запись `AJ-seed-alias-decline` пере-заякорена.** Моя правка сдвинула строку, на которую она указывала, и инструмент падал жёсткой ошибкой «the source moved under the catalogue» — это был бы хард-блокер для следующей сессии. Атакуемое свойство сохранено дословно (снять дельта-оговорку, оставить остальное); плюс добавлены шесть СВОИХ посадок `SH1`…`SH6`, по две на пункт, чтобы фикс был постоянным гейтом, а не разовым замером. - **Не проверено:** поведение под реальным параллелизмом двух `bank-apply` (лок это исключает, но я не гонял); поведение склейки на провайдере, который принимает одно системное И имеет префиксный кеш (у gemini кеш иной) — довод «склейка кеш не портит» опирается на комментарий самого кода и на то, что префикс сохраняет байты и позицию, живьём не мерен. **§4 ОПРОВЕРГАТЕЛЬ — ЗАКАЗ ИСПОЛНЕН, И ОН ОКУПИЛСЯ. Шесть линз, мандат «опровергни».** Веер шесть, а не «2–4» из §4: численный потолок снят словом владельца 28.08, соразмеряю предмету. Линзы: Anthropic-путь и кеш · читатель книги · провод `withheld`/`translated` · безопасность банковых решений · пара, которой в репо нет + детерминизм · вырожденность собственных тестов. **Пять из шести вернули вердикт REFUTED или PARTIAL, и это правильный исход: они нашли ШЕСТЬ дефектов, из них ЧЕТЫРЕ я внёс сам этим паком.** Все воспроизведены ИСПОЛНЕНИЕМ на копиях дерева, не чтением. | Находка | Кто/чем доказано | Что сделано | |---|---|---| | **MAJOR: маркер называл ЧУЖУЮ причину.** Юнит, где редактура флагнулась САМА (косм. чистка) И выпал член, печатал причину ЧИСТКИ как причину дыры: «фрагмент не переведён: sanitizer_stripped». Та же ложь, что убирал пак, перенесённая в поток текста | 2 линзы независимо, боевой бинарь | **ИСПРАВЛЕНО:** заведены `DroppedReason` на `ChunkOutcome`/`ChunkExport`, маркер несёт причину ЧЛЕНА, баннер — вердикт ЮНИТА. Пин `SH7`, состояние в матрице обоих рендеров | | **MAJOR: анти-скоуп.** В черновично-однофазном конвейере юнит — одиночный чанк, и его СОБСТВЕННЫЙ флаг читался как «выпавший член» ⇒ косметически почищенный чанк, у которого ВЕСЬ текст на месте, объявлялся неполным | боевой бинарь, `tmctl translate` и `tmctl export` расходились на одном юните | **ИСПРАВЛЕНО:** учёт выпадений только когда финальная волна — редакторская (`export.go`). Тест `TestADraftOnlyStrippedChunkIsNotCalledIncomplete`, пин `SH8` | | **MAJOR: перестановка порядка записи купила сходимость ЦЕНОЙ второго тихого вреда.** Rejects-first оставляет строку дельты на диске, а `seedGlossary` дельту отказами НЕ фильтровал ⇒ в окне между падением и ре-сендом ОПЛАЧЕННЫЙ прогон вливал в глоссарий редактора терм, который владелец отклонил | 2 линзы, исполнение на реальных файлах | **ИСПРАВЛЕНО:** `loadMinedDelta` отбрасывает поверхность, стоящую в списке отказов, и говорит об этом WARN'ом. Тест на реальном полусостоянии, пин `SH12`. Заодно исправлено утверждение в шапке `writeDecisionFiles`, которое после перестановки стало ложным | | **MAJOR: сходимость останавливалась на ОДНОЙ из двух дверей.** `refuseInertDeclines` я решил не трогать — и ошибся: траектория из ОДНИХ ПРИНЯТЫХ вызовов (decline терма-со-своей-строкой → approve терма, который несёт его алиасом) делала повтор стоячего реестра решений отказным навсегда, а всё-или-ничего уносило с ним законный approve | исполнение, полная трасса | **ИСПРАВЛЕНО:** та же оговорка на второй двери (по `in.Rejects`, не `res.Rejects` — фолд уже отработал). Тест `TestTheStandingLedgerConvergesThroughBothDoors` + направление-гард на живом алиасе, пин `SH13` | | **MAJOR: у боевого объявления в `configs/models.yaml` не было ГЕЙТА вовсе.** Две строки — единственное, что доводит фикс до реального эндпойнта, а удаление их оставляло ВЕСЬ модуль зелёным | опровергатель посадил свою мутацию и получил SURVIVED | **ИСПРАВЛЕНО:** `TestShippedGeminiDeclaresOneSystemMessage` над боевым конфигом, пин `SH9` (проверено: удаление строк → тест краснеет) | | **MINOR: гарды в `renderTranslate` не были запинены** (близнецы в `renderExport` были), `case "multi"` не покрыт, счётчики читались как разбиение целого | исполнение | **ИСПРАВЛЕНО:** матрица шести состояний для `renderTranslate`, тест `TestExplicitMultiCostsNothing`, счётчик переписан как `exported=N (of them incomplete=M)`. Пины `SH10`, `SH11` | **ЧТО ОПРОВЕРГАТЕЛИ АТАКОВАЛИ И НЕ СЛОМАЛИ** (чтобы следующий читатель знал, что уже покрыто): Anthropic-путь (склейка до него не доходит структурно — `Cap` в него не передаётся; два блока и живой `cache_control` на месте) · префиксный кеш (стабильный префикс сохраняет байты и позицию) · провод `translated`/`withheld` (`UnitDone.Shipped` считается по пустоте текста; ни `DroppedMembers`, ни маркер её не трогают) · отсутствие новых причин флага (константы `FlagReason` до и после ИДЕНТИЧНЫ) · `exportVersion` не двигался · снапшоты боевых конфигов не двигались · арм-конфиги грузятся. **ЧТО НАЙДЕНО, НО СОЗНАТЕЛЬНО НЕ ЧИНИТСЯ — с аргументом.** 1. **Ловушка `capabilities` на `kind: anthropic`** — оказалась чинибельной дёшево, и я её ВЗЯЛ: ось теперь отказывается на этом виде по образцу `cache_ttl` (`models.go`), с тестом. Пред-существующий класс для ОСТАЛЬНЫХ осей (`budget_field`, `temperature`, `reasoning`, `min_max_tokens`) остаётся — его правильная форма тащит `kind` в `validateCapabilities` и трогает каждую ось; это чужой предмет. 2. **Снапшот не покрывает модель ГЕЙТА ТЕРМИНОЛОГИИ** (`config/pipeline.go` — не фолдится сознательно, ратифицированно), а терминолог шлёт ДВА системных. ⇒ флип оси у провайдера, которым пользуется только этот гейт, меняет байты под НЕИЗМЕННЫМ `RequestHash`, и чекпойнты ложно попадают. Дыра НЕ моей оси — по ней ездят все оси `capabilities` с D3.1 — но моя первая, чей смысл именно в смене этих байтов. **Утверждение в комментарии я ослабил до правды и дыру НАЗВАЛ прямо в коде** (`capability.go`); закрытие — фолд модели гейта, как это делает `repairSnapshot`, и это правка снапшот-контракта, принадлежащая владельцу решения «не фолдить», а не провайдерскому квирку. 3. **Сузив отказ по поверхности, я потерял поучение в одном углу:** decline, который И записан, И по-прежнему инертен против подписанного сид-терма, теперь отвечает `already_applied` вместо единственной работающей инструкции «убери из glossary_seed». Сходимость сочтена более тяжёлой обязанностью (у вызывающего, который следует опубликованному ретраю, другого хода нет), но размен реальный: **закрывается полем отчёта, несущим стоячий факт**, и это форма шва. **ХАРД-БЛОКЕР, НАЙДЕННЫЙ И СНЯТЫЙ (требование владельца «после тебя не осталось хард-блокеров»).** `go test ./cmd/tmctl/` строил боевой бинарь в `os.MkdirTemp` и НИКОГДА не убирал каталог — по 20 МБ за прогон. За этот пак накопилось **249 брошенных каталогов `tmctl-bin*`, ~5 ГБ**, и `/tmp` заполнился до 99%: батарея под `-race` начала падать с `link: mapping output file failed: no space left on device`, что читается как сломанная сборка, а не как кончившееся место. Я на это и купился на один прогон — сообщать «батарея красная» было бы ложью. **Причина найдена, место освобождено, харнесс починен:** добавлен `TestMain`, снимающий каталог (`cmd/tmctl/exitcontract_test.go`); проверено исполнением — два прогона подряд, прирост каталогов 0. Дефект пред-существующий, пак его лишь довёл до отказа. **ЧИСЛА, С КОМАНДАМИ (§4.1).** - `go build ./...` · `gofmt -l .` (пусто) · `go vet ./...` · `go vet -tags live ./...` — **все 0**. - `go test ./... -count=1 -timeout=20m` → **EXIT 0**. - `go test ./... -race -count=1 -timeout=20m` (= строка `make test`) → **EXIT 0, real 2m09s**. - `golangci-lint run --timeout=15m ./...` (= `make lint`) → **0 issues**. - `go test ./internal/pipeline/ -run TestGolden -count=1` → **ok** (голден пинит ПОЛНОЕ тело запроса). - `go run ./cmd/tmmutate -root <КОПИЯ>` → **73 посадки, 0 неожиданных исходов, EXIT 0** (72 RED + один каталогизированный выживший `G-byte-gate`; все 13 моих красны); каталог вырос 60 → 73 (13 новых посадок: `SH1`…`SH13`), у каждой из трёх заказанных тем ≥2 своих. ⚠ **Каталог гонять ТОЛЬКО на копии** — сам инструмент это требует, а я один раз запустил его на рабочем дереве и словил таймаут; дерево спас обработчик сигнала самого инструмента (проверено: мутационного текста не осталось, батарея зелена). Записываю как урок, а не как мелочь. **ФАЙЛЫ (19 правленых + 5 новых; всё в `backend/`, плюс эта запись в журнале).** `cmd/tmctl/render.go` · `cmd/tmctl/exitcontract_test.go` · `cmd/tmmutate/mutations.json` · `configs/models.yaml` · `internal/config/models.go` · `internal/config/models_catalog_test.go` · `internal/llm/capability.go` · `internal/llm/httpllm.go` · `internal/llm/provider_openai.go` · `internal/llm/provider_local.go` · `internal/membank/decisions.go` · `internal/membank/decisions_fuzz_test.go` · `internal/pipeline/bankdecisions.go` · `internal/pipeline/bankreport_test.go` · `internal/pipeline/bookrun.go` · `internal/pipeline/export.go` · `internal/pipeline/mining.go` · `internal/pipeline/runner_test.go` · `internal/pipeline/waverun.go`. Новые: `cmd/tmctl/exportgap_cli_test.go` · `internal/llm/systemmessages_test.go` · `internal/membank/decisions_converge_test.go` · `internal/pipeline/bankdecisions_converge_test.go` · `internal/pipeline/systemmessages_wave_test.go`. **⛔ ВОПРОСЫ И ПИНГИ ОРКЕСТРАТОРУ — четыре, каждый требует ЧУЖОГО решения.** 1. **Изменён СУЩЕСТВУЮЩИЙ тест, и я это заявляю, а не прячу.** `bankreport_test.go` `TestHalfLandedIsNamedPerFile` кодировал в фикстуре ПОРЯДОК переименований, который заказ §3.3 изменил; фикстура пере-нацелена на второй rename, утверждение сохранено дословно и усилено новым `TestTheRenameOrderIsWhatMakesADeclineRecover`, пинящим сам порядок с его причиной. **Считаешь подгонкой — откачу, и тогда половину §3.3 с полусостоянием надо выносить отдельным заказом.** 2. **Вендор-вердикт по Gemini** — прислан отдельным сообщением, с URL и датами; три поправки к `00-provider-quirks.md:157` (структурная посылка ≠ поведенческий вывод · `Content.parts[]` повторяемо · «форум Google» — это два не-сотрудника из 2025). Пишешь ты, зона твоя. 3. **Дыра снапшота у модели гейта терминологии** (см. выше, п.2 «не чинится»). Закрытие — правка снапшот-контракта; принадлежит владельцу решения «гейт не фолдится», не мне. 4. **Д1 достоин собственного заказа.** Не «расширить decline до энтити» (это против канона), а узко: эмиссия майнера энтити-широка (`miner_emit.go` `clusterTouches`), а фильтр авто-банка ключуется только по собственному src строки (`mining.go:662`), поэтому ОТКЛОНЁННАЯ поверхность возвращается алиасом уцелевшей строки. Канон уже сегодня обещает «declining a surface removes EVERY window of that surface» — то есть движок не держит обещание. Правильная форма — снять алиас со строки, а не снести строку; это банковая онтология, и в «тихую порчу» её тянуть не следует. **ПРАВО §9 ПРИМЕНЕНО ТРИЖДЫ, каждый раз с грунтом:** Д3 отклонён (символ живёт в `platform/`, зона заперта; движковая половина здорова и порядок «карта → память» исполнен) · Д1 отклонён как отдельный предмет (выше) · подсказка промта про место маркера отклонена (она перевернула бы `withheld` → `translated`). ⚠ И один раз право §9 я применил ОШИБОЧНО: решил не трогать вторую дверь `refuseInertDeclines`, рассудив, что её случай — «устаревший документ, а не ретрай». Опровергатель показал траекторию из ОДНИХ ПРИНЯТЫХ вызовов. Рассуждение было неверным, исполнение это доказало, дверь починена. **⚠ ЗАПИСКА НИЖЕ — ПЛАН ДО ПРАВОК (§6), она СОХРАНЕНА как след дисциплины, но её выводы местами ОПРОВЕРГНУТЫ итогом выше. Не наследовать из неё: «сломан только класс (а)» (сломан ещё и стык «редактура флагнулась сама + выпал член»), «фиксация порядка записи безопасна» (она купила второй тихий вред, закрытый фильтром дельты) и «Д3 не существует» (существует, в `platform/`).** **📝 ЗАПИСКА-ПЛАН БЭКЕНД-СЕССИИ 28.08 — ПАК «ТИХАЯ ПОРЧА» (`docs/BACKEND_SILENT_HARM_SESSION_PROMPT.md`), §6. Правок кода на момент записи НЕТ.** Три заказа: §3.1 инъекция банка не теряется · §3.2 дыра выдачи видна читателю файла · §3.3 повтор принятого документа сходится. Ниже — что установлено ЧТЕНИЕМ до правок и как из этого следует план. ⚠ Всё, что помечено «проверить исполнением», в план входит как обязательство, а не как факт. **Находка 1 (меняет §3.2). Маркер уже есть — и он ВРЁТ.** `cmd/tmctl/render.go:499` печатает для флагнутого юнита с НЕПУСТЫМ текстом баннер «(leak cleaned, verify)», а `:38` — «[FLAG … — leak cleaned, exported cleaned, verify]». Ветка выбирается по `ce.FinalText != ""`, и c-lite member-drop (`export.go:226-230` ставит `DispFlagged` при непустом тексте) попадает ровно в неё. То есть юнит, у которого ВЫПАЛ ЦЕЛЫЙ ЧЛЕН, объявляется читателю косметической чисткой санитайзера, которой не было. Существующий тест это не ловит: `cmd/tmctl/export_cli_test.go:66` покрывает ветку только причиной `sanitizer_stripped`. ⇒ Предмет §3.2 не «завести маркер», а «перестать врать и покрыть непокрытое» (пере-формулировка разрешена оркестратором 28.08 при закрытом гейте «что читает человек»). **Гейт §3.2 закрыт чтением: движок НЕ ПИШЕТ ФАЙЛОВ ВООБЩЕ.** Все команды рендерят в stdout (`cmd/tmctl/main.go:227-228`, `:338-349`). Читаемых человеком поверхностей у движка две, обе текстовые и обе уже несут пер-юнитный баннер: `tmctl translate` (`render.go:27-72`) и `tmctl export --plaintext` (`render.go:478-511`). Дефолт `export` — JSON (`export_cli_test.go:24-28`), и он же артефакт шва к платформе (`research/23` строка 76 — «артефактный контракт (export-JSON · 101 · 100)»). Читательский файл (epub и пр.) собирает ПЛАТФОРМА по `POST /books/{bookId}/exports` (`14-api-contract/openapi.yaml:824-870`) — чужая зона. ⇒ Адресат моего маркера — человек, читающий plaintext-выгрузку движка; платформенная половина остаётся за платформой, и провод различение несёт (`14-api-contract/README.md:300`). **Следствие для места правки, и оно расходится с подсказкой промта.** Промт указывал на `export.go:237` (прецедент `ApplyHeading`). Туда маркер класть НЕЛЬЗЯ: `ChunkExport.FinalText` — это поле `final_text` артефактного контракта, а `waverun.go:203-204` кормит провод предикатом `oc.FinalText != ""` (`shipped`). Любой маркер, вклеенный в текст ПУСТОГО юнита, перевернул бы `withheld` → `translated` — ровно ⛔(а) промта. `ApplyHeading` этого не делает только потому, что на пустом тексте он no-op (`chunk/chunker.go:186-191`). Различение: титул — часть КНИГИ и обязан ехать на провод; маркер дыры — метаданные О тексте, и в поле, по которому провод судит, ему нельзя. ⇒ маркер живёт в РЕНДЕР-слое (`cmd/tmctl/render.go`), на слой дальше проекции. Это ответ на требование промта «либо тем же механизмом, либо объясни, почему другое». **Классы дыр — ревизия по коду.** (а) c-lite member-drop — помечен, но ЛОЖНО (находка 1); (б) юнит целиком withheld (`waverun.go:496-509`) и (в) gate-override `glossary_miss` (`export.go:319-323`) дают пустой текст и попадают в ветку `render.go:502-504` «(not translated, flagged for a human)» — помечены ВЕРНО, работы не требуют; (г) четвёртая, сноска «[1] …» мимо детектора примечаний: детектор требует заголовочного слова (`lang/data/target-ru.txt`, `sanitizer_trailing_note`), но ханьцзы в сноске поднимают класс `CJKLeak`, а он `CosmeticOnly` (`checks/sanitizer.go:161-163`) ⇒ юнит уходит как `sanitizer_stripped`, и баннер «leak cleaned» для НЕГО правдив. То есть класс (г) накрыт существующим механизмом БЕСПЛАТНО и работы в детекторе не требует — граница промта соблюдена. **Проверить исполнением обязательно:** что сноска с ханьцзы действительно даёт `CosmeticOnly`, а не подтягивает субстантивный класс. **§3.1 — план и его цена.** Свёртки в `toOpenAIMessages` нет (`llm/httpllm.go:513-521`, копия один-в-один); её комментарий `:511-513` говорит, что `CacheBoundary` на этом пути бессмысленна ⇒ свёртка кеш не портит. У Anthropic путь другой и трогать его нельзя (`llm/provider_anthropic.go:169-179`: ведущий system-префикс → блоки, `CacheBoundary` → реальный `cache_control`). Ось лечения — `llm.Capability` + блок `capabilities:` (провайдерный ИЛИ модельный, `config/models.go:85`,`:146`,`:442-450`), прецедент безопасного расширения — `MinMaxTokens` с `json:",omitempty"`. **Детерминизм:** снапшот фолдит РАЗРЕШЁННУЮ Capability только для праймари и единственного `escalate_to` (`pipeline/snapshot.go:316-340`,`:479`,`:495`); в `pipeline-c1.yaml` `escalate_to: deepseek-v4-pro` (`:60`), а gemini стоит лишь третьим в цепочке `default` (`:129`) при `budget_usd: 0` (`:136`) и раннере SINGLE-HOP ⇒ объявление квирка у gemini снапшот боевого конфига НЕ двигает. Проверить исполнением (голдены + снапшот-тесты). ⚠ **Честная граница достижимости:** при SINGLE-HOP и текущих конфигах gemini не достигается ВООБЩЕ; дефект латентный и ждёт конфига, который назовёт gemini в `escalate_to` или в `chain[0]` лейбла. ⚠ **Развилка, которую выношу на опровергателя (§4 заказ):** дефолт оси. «Квирк объявляет тот, у кого он есть» (нулевое значение = сегодняшнее поведение) не двигает ничего, но НОВЫЙ провайдер с той же ловушкой снова теряет инъекцию молча — тот же класс тихого вреда. «Свёртка по умолчанию, исключение объявляет проверенный» безопасна для незнакомого провайдера, но меняет провод боевому переводчику без замера (запрет «эмпирика решает») и двигает снапшот. Решение — после опровергателя, с обоснованием. ⚠ **Посылка промта «Gemini принимает ровно одно системное» ОТОЗВАНА автором** (оркестратор, 28.08): это было рассуждение; собственный замер полигона (`00-provider-quirks.md:157`) даёт 535 токенов ≈ не выжило НИ ОДНО. Заказу это безразлично, тесту — нет: утверждается пост-фиксный инвариант «системное РОВНО ОДНО и инъекция ВНУТРИ него», он верен при любом чтении. Вендор-вердикт со ссылкой уходит оркестратору для `00-provider-quirks.md` (его зона). **§3.3 — трасса контрактной сессии ПОДТВЕРЖДЕНА моим чтением, и подтверждено то, что она сама не читала.** Отказ живёт в `membank/decisions.go:374-377`, бежит ДО фолда (`:296-300`), и всё-или-ничего вытирает документ целиком (`:302-307`). **Порядок записи я прочитал сам: `bankdecisions.go:458-473` коммитит ДЕЛЬТУ ПЕРВОЙ**, а собственный комментарий функции `:436-438` говорит дословно: «delta-first is safe for an approve and NOT for a decline … convergence is what actually carries both». ⇒ окно застревания реально, и код сам называет допущение, которое ложно. Идемпотентная ветка уже есть (`already_applied`, `:221-224`,`:269-272`) — вводить понятие не надо. План: сузить условие отказа так, чтобы оно не срабатывало, когда отказ УЖЕ записан в rejects, плюс направление-гард (настояще-инертный decline обязан отказывать по-прежнему тем же текстом). Идентичность документа НЕ вводить — сходимость по СОСТОЯНИЮ. **Д1 и Д3 — беру право §9 и НЕ чиню, оба с грунтом.** · **Д3 ОТКЛОНЁН как несуществующий:** гард `LiftBankStop` не найден в коде НИ В КАКОМ написании (греп по всему `backend/`); реальный носитель — `mining.go:266-277`, и он уже реализует ратифицированный порядок «карта → память»: `writeFileAtomic` карты на `:191`, `MarkStopPresented` на `:274`, с комментарием `:270-273` «strictly after the map … the reverse order can mark surfaces presented whose map never reached disk» и с явной оценкой отказа памяти как «one extra stop, nothing lost» (`:275-276`). То есть механизм написан ВМЕСТЕ с памятью v16, а не до неё. · **Д1 ОТКЛОНЁН как ратифицированное поведение + названный остаток:** канон определяет decline ПОВЕРХНОСТНО («declining a surface removes EVERY window of that surface» — `14-api-contract/openapi.yaml:2051`), и расширение до энтити противоречило бы контракту; а «память глушит стоп» — уже НАЗВАННЫЙ и принятый двусторонний остаток в комментарии `mining.go:308-313`. Оба — строка с причиной, не правка. **Сосед 131 (называю, не чиню, как велит §3.4):** кап инъекции считает только «src → dst» и не считает маркер `⟨проверить⟩` и родовые пометки (`lang/data/injection.txt` несёт их как отдельные значения) ⇒ реальная инъекция шире бюджета. Проверю точный носитель при работе в этом коде и назову `file:line` в итоге; чинить не буду — тема денег и качества инъекции, а не тихого брака в тексте. **Порядок работы:** §3.1 → §3.2 → §3.3, каждый с тестом на ПОСТ-фиксный инвариант; батарея под `-race` + линтер; посадки мутаций ≥1 на пункт с вердиктом по ДЕЛЬТЕ и ТОПИЧНОСТИ; опровергатель на выбор места §3.1 (заказ §4). Веер агентов соразмеряю предмету: численный потолок «2–4» из §4 промта снят словом владельца 28.08. **✅ ИТОГ БЭКЕНД-СЕССИИ 27.08 (2) — ФИКС3 ПО ФИКС-ЛИСТУ ПРИЁМКИ (`docs/BACKEND_SEAM_FIX3_SESSION_PROMPT.md`), все 6 пунктов. Дерево готово, НЕ закоммичено; продолжает дерево трёх паков.** Четыре пина, один снос, ноль новых механизмов: пин порядка «карта → память» (мутация приёмки воспроизведена в трёх пакетах ДО пина; занятый путь карты → ошибка прогона + пустая память) · пин «отчёт класса 15 мерит подпись по ДИСКУ, не по намерению» (утверждение панели воспроизведено) · оба носителя старого пола полосы приведены к двух-ярусной букве · отменённая формула «стоп гаснет, когда решён каждый терм» зачищена ГРЕПОМ — восемь носителей, включая два файла самого пака · снесена машинная таблица `.bank-stop.json` вместе с писателем и четырьмя тестами (читателя нет ни в одной зоне — свой греп; человеческая `.bank-stop.txt` не тронута) · банк-экспорт получил якорь свежести `as_of`+`run_id` (аддитивно на `tm-bank-v1`; «может быть устаревшим» больше не живёт только в логе). **Доказательная база — `FINDINGS` §46; каталог мутаций 58 → 60 (FX23, FX24).** **⛔ Пинги:** 1. **Платформе/оркестратору:** `.bank-stop.json` СНЕСЁН (мёртвая публикация упразднённого D39.144 пер-термного экрана) — платформенные журналы (`platform-PROGRESS.md`, `STACK_DECISIONS.md`) называют его атомарно-читаемым сайдкаром, упоминания устареют при лендинге. 2. **Потребителям банк-экспорта:** документ несёт `as_of` (граница прогона) и `run_id` (тот же id, что стрим `events.jsonl`); аддитивно, старые читатели не ломаются; пустой `run_id` = «идентичности нет», не «текущий». **✅ ИТОГ БЭКЕНД-СЕССИИ 27.08 — ДОФИКС-2 «ФЛАЖОК СТАНОВИТСЯ ФЛАЖКОМ» (`docs/BACKEND_SEAM_FIX2_SESSION_PROMPT.md`). Дерево готово, НЕ закоммичено; продолжает дерево паков 24–25.08.** Все 13 пунктов §4 закрыты: движок сам реализует модель D39.144 — стоп банка срабатывает только на кластер, которого ни один прежний стоп не предъявлял (персистентная память `bank_stop_presented`, схема v16); авто-провод неподписанных строк работает и при ПОДНЯТОМ флаге; отчёт двери собирается ОДНИМ конструктором по явному исходу (проекция · отказ · no-op · прерывание · записано · запись не удалась), версия отчёта → v2 (`written_delta`/`written_rejects`, mode `refused`/`write_incomplete`); полоса отказов получила два яруса и класс `write_incomplete` (exit 15); оба файла решений готовятся целиком до первого rename + fsync каталога; ключи `mined_delta`/`mined_rejects` СНЯТЫ (пути только конвенцией — МЕЖКНИЖНАЯ гонка недостижима, ⛔-пинг прошлого пака закрыт; воркфлоу-ревью нашло остаток той же оси: второй конфиг ТОЙ ЖЕ книги с другим объявленным project_db пишет те же файлы под другим локом, мимо flock живого прогона — пинг ниже, решение не паку); карта подписи пишется атомарно и несёт конверт `map_version`+`map_id`; шесть слепых пятен гейта закрыты пинами, проверенными посадкой. Поверх — воркфлоу-ревью (шесть полос × три скептика, `FINDINGS` §45): его две кодовые находки закрыты правками с пинами — `--dry-run` теперь отказ для всех глаголов без проекции (раньше `translate --dry-run` МОЛЧА запускал платный прогон), а повтор после отказа синка каталога держит класс 15, пока долговечность не пере-доказана. **Доказательная база — `backend/docs/SEAM_PACK_FINDINGS.md` §§36–§45; каталог мутаций 36 → 58 записей, 0 неожиданных исходов.** Здесь — только требующее действия. **⛔ ПОРЯДОК ДЕПЛОЯ/ЛЕНДИНГА — зависимость НАПИСАНА, а не выведена (заказ §4.12):** 1. **Движок первым, платформа второй.** Сброс флага `--verify-bank` платформой (её персистентная колонка + особый случай в спавне, `platform/internal/runs/spawn.go`) может умереть ТОЛЬКО ПОСЛЕ лендинга этого пака: в обратном порядке каждая книга с поднятым флажком в день лендинга перестаёт писать авто-банк и пере-сидироваться ровно в режиме, ради которого флажок существует. 2. **`tmctl migrate` до прогонов** (голова схемы v15 → v16): `OpenReadOnly` отказывает классом 13 всем непере-мигрированным книгам — ратифицированный порядок D39.134 п.3 это покрывает, но назван явно. 3. **Миграционная нота:** после апгрейда память пуста → ОДИН доброкачественный стоп на первом verify-прогоне каждой книги. **⛔ ПИНГИ ЧУЖИМ ЗОНАМ (правок не делал):** 1. **Платформе:** у неё умирает сброс флага при resume (после лендинга движка — п.1 выше) · отчёт двери теперь v2 (`tm-bank-decisions-report-v2`: mode `refused`/`stopped`/`write_incomplete`, пофайловые `written_*`) — её `readEngine` выбрасывает stdout при ненулевых кодах, прежний пинг об этом стал острее: класс 15 БЕЗ отчёта неотличим от «ничего не легло» · новый exit 15 в полосе 10–19 читается её ридером как refused по построению полосы — ратификация значения на оркестраторе при лендинге. 2. **Полигону:** `eval/bank_autonomy/parse_common.py` парсит `mined-signature.yaml` — карта теперь несёт верхние ключи `map_version`/`map_id` (лежащие артефакты coldrun не тронуты; новые прогоны будут в новом жанре). 3. **Фронту:** экран подписи, читающий `signature.undecided` из отчёта двери, обязан перестать трактовать его как «стоп не погаснет» — поле информационное (D39.144), контракт поля пере-описан в коде. 4. **Майнерский хвост за потолком структурно недостижим** (§4.4, НЕ чинилось по заказу): потолок эмиссии (200) режет ранжированный пул ДО фильтров пригодности/сида/отказов (`miner_emit.go`, `ranked[:emitRankCap]` до `emissionEligible`). Пере-замерено своей командой (числа прежнего сплошного читателя в дерево не попали): 2354 ранжированных · 209 годных во всём алфавите · 11 в окне потолка · 198 невидимы постоянно; отклонение всех годных окна не подняло ни одного из хвоста (`FINDINGS` §38). Первоисточник замера — постоянный якорь-пин `internal/miner/emissioncap_test.go`: он утверждает ТЕКУЩЕЕ поведение и покраснеет в день сдвига потолка или порядка фильтров, приведя двигающего к этому пингу. Двигать потолок — трогать фрозен-паритет; отдельное решение. 5. **Гипотеза «подписание промахивается мимо чекпоинтов терминолога и пере-оплачивает роль» механизмом НЕ подтверждена** (§4.13): память предъявленного входов чекпоинта не трогает; если перерасход существует — он существовал и до пака, отдельный предмет. 6. **Остаток оси арбитра (нашло воркфлоу-ревью, воспроизведено живьём):** второй конфиг ТОЙ ЖЕ книги (один каталог, один book_id, другой объявленный `project_db`) пишет ту же пару файлов решений под ДРУГИМ локом — в т.ч. мимо flock живого прогона. Межкнижная версия снята конвенцией; эта — второй конфиг одной книги, т.е. рукотворная конфигурация оператора. Выбор лечения (второй арбитр на идентичности файлов решений или отказ при загрузке) трогает закон п.2 — решение не паку, пинг. Довод в коде сужен честно (`bankdecisions.go`, THE ARBITER). **✅ ИТОГ БЭКЕНД-СЕССИИ 25.08 — ДОФИКС ТОЙ ЖЕ ДВЕРИ по приёмке (`docs/BACKEND_SEAM_FIX_SESSION_PROMPT.md`). Дерево готово, НЕ закоммичено; продолжает дерево пака 24.08, а не заменяет его.** Все 16 пунктов §4 закрыты: блокер формата (дверь писала байты, которых её же парсер не читает, и любой следующий вызов по книге — включая проекцию — падал) · пред-существующие поломки чинятся по одной · два ключа на один путь · гарды флагов на ПРИСУТСТВИЕ (`--decisions=` уходил на ПЛАТНЫЙ путь) · кап документа решений · конверт путей на `manifest --json` · алиасы судятся по НАБОРУ · перевёрнутое окно глав · честность отчёта о стопе · SIGTERM · десять слепых пятен гейта · **цепь целиком в репозитории**. **Доказательная база, замеры, попунктный чек и обе итерации ревью — `backend/docs/SEAM_PACK_FINDINGS.md` §§14–30;** здесь только то, что требует ДЕЙСТВИЯ. **✅ ⛔-ЗАКАЗ ПРИЁМКЕ ИЗ ПАКА СНЯТ.** Цепь «стоп → `bank-apply` → резюм → стоп ГАСНЕТ» теперь живёт в репозитории как $0-тест на фейковом провайдере (`internal/pipeline/bankchain_test.go`), на ОДНОМ проекте от стопа до погасшего стопа. Довод пака «своими силами невозможно, надо дойти оплаченной черновой волной» был шире правды: харнесс для этого уже был в репозитории. Мина №1 строки **199** (ливлок на `status: auto`) закрыта исполнением, а не рассуждением. **⚠ РАТИФИКАЦИЯ КЛАССА 14 — прежнее требование в силе, объём немного шире.** Класс `decisions_rejected` теперь несёт и отказ по КАПУ (документ больше, чем движок применяет за один акт): это факт о ЗАПРОСЕ, а не о деплое, поэтому 10 был бы неверен. Носители полосы поднимает оркестратор при лендинге, как и планировалось. **⛔ ПИНГИ ДОФИКСА (правок не делал):** 1. **`exit 2` при OOM платформа читает как успех** — общедвижковый класс; кап удерживает `bank-apply` от того, чтобы стать первым, кто до него доедет, но самого класса не снимает. 2. **Каталог книги без права записи даёт `exit 1`** (вне полосы). Класс движка корректен и одинаков у всех глаголов — чинить надо чтение кода 1 на платформе, которая списывает проблему прав ХОСТА в бюджет попыток КНИГИ. 3. **Проекция отвечает «что применится», а не «почём».** Рекомендация — НЕ строить: глагол $0 по построению, поле «$0» было бы шумом. Решать оркестратору. 4. **Карта подписи не удаляется после погасшего стопа** — пред-существующее; зафиксировано тестом как текущее поведение. Честный ответ на «есть ли что подписать» — новое поле отчёта `signature.undecided`. 5. **Цена глагола Θ(N²) по построению** — каждый пер-решенческий помощник сканирует весь документ. Именно поэтому кап на КОЛИЧЕСТВЕ стоит на 5 000. Поднимать его без индексации этих сканов нельзя. 6. **Апстрим-дефект `gopkg.in/yaml.v3 v3.0.1`** (эмиттер пишет блочный скаляр, который его же парсер не читает) вылечивается по-настоящему только скаляр-типом схемы с явным стилем или переездом на `go.yaml.in/yaml/v4` (релиз-кандидат, смена module path в ДВУХ зонах). Мой обход несёт ссылку на исходник библиотеки и характеризационный тест, который покраснеет в день починки апстрима. 7. **Фолд поверхностей Go-резидентный:** `internal/text/norm.go` держит `trad2simp` и катакана→хирагана в Go, а не в langpack'е. Пара, которой в репо нет, деградирует до NFKC+lowercase — не блокирована, но это ровно тот класс, о котором ревью-вопрос общности. Шире двери, поэтому пингом. **⚠ ЧТО В ЗАПИСИ ПАКА 24.08 (ниже) УСТАРЕЛО — читать её через эту поправку.** 1. **Пинг 2, «таймаут: 1.6с на 5 000 термов, 9.1с на 20 000» — ЧИСЛА НЕВЕРНЫ, и это ровно тот датум, которым платформенный пак будет мерить свой таймаут.** Мой замер той же командой: 5 000 `approve` → **5.2 с**, 20 000 → **50 с**; на минимальных `decline` (самая дешёвая форма, и потому та, которой байтовый кап пускает больше всего) 5 000 → **11.3 с**, 12 000 → **58 с**, 28 900 → **308 с**. Брать надо `FINDINGS` §18 и §29 п.1, не эти. 2. **Пинг 6 «однострочник при лендинге: документ решений не капирован» — СДЕЛАН.** Капов теперь два (1 МиБ на чтение + 5 000 решений на применение), потому что байтовый не ограничивает время. 3. **Пинг 3 про флейки** пере-снят сильнее: изолированный A/B против дерева HEAD даёт побайтно одинаковое падение с обеих сторон — `FINDINGS` §31. **⛔ ПИНГИ, КОТОРЫЕ ЖИЛИ ТОЛЬКО В `FINDINGS` И НЕ ДОЕЗЖАЛИ ДО ЖУРНАЛА** (найдено проверкой согласованности): карта подписи пишется обычным `os.WriteFile` мимо атомарной конвенции сайдкаров того же пакета (пред-существующее) · цена доказывающего рендера ≈5 % времени, плюс `notCanonical` теперь тоже разбирает документ · опубликованные пути двух файлов решений не несут версионного конверта (пинг пака §10.5) · схема сида без `omitempty` (пинг пака §10.6). **⚠ Два пред-существующих `-race`-флейка в `internal/store` не тронуты** (`TestKillMinus9LosesAtMostOneCall`, `TestTheSeamIsNotChargedToTheStoreOperationBudget`). База ДО правок, снятая на нетронутом дереве пака, уже была красной по первому — это моя собственная атрибуция, а не наследованная. **⛔ ПИНГ, КОТОРЫЙ ТРОГАЕТ РАТИФИЦИРОВАННЫЙ ЗАКОН (п.2) — решать не паку.** Арбитр двери — flock на `.lock`, а пишутся `mined_delta`/`mined_rejects`, которые `book.yaml` может направить куда угодно. **Две книги с РАЗНЫМИ `project_db` и ОДНИМ delta друг друга не исключают:** воркфлоу-ревью продемонстрировало гонку на 1500 одобрений с каждой стороны — одна сторона потеряла всё, `exit 0`, отчёт «применено». Внутрикнижную версию этого §4.3 уже запрещает; межкнижная законна сегодня. Выбор — второй арбитр или отказ при загрузке на путь вне каталога книги. В корпусе нарушителей нет. В коде стоит явное «арбитр покрывает проект, а не пути», а не молчаливое допущение. **🔧 В репозиторий добавлен инструмент: `backend/cmd/tmmutate`** — харнесс мутаций с каталогом из 36 посадок, у каждой названо СВОЙСТВО, за которое она отвечает, и записан ожидаемый исход. Три сессии подряд писали такой инструмент в скретчпад и теряли его; приёмка при этом нашла восемь мест, где гейт был слеп. ⚠ **Он дважды подводил и оба раза был починен по следу:** сначала подсадил мутацию и прошёл собственную sha256-самопроверку (эталон второй правки снимался с уже пропатченного файла), затем считал КРАСНЫМ провал СБОРКИ — из-за чего две записи каталога, сломанные рефакторингом, целую итерацию числились пойманными тестами, которые не могли отработать. Теперь: одно чтение и запись на файл, отпечаток всего каталога перед каждой посадкой, зелёная базовая линия пакета ДО первой посадки и третий вердикт `NOTHING`, роняющий гейт. `FINDINGS` §25 и §34. **✅ ИТОГ БЭКЕНД-СЕССИИ 24.08 — ПАК «ВХОДНАЯ ДВЕРЬ ШВА», движковая половина D39.156. Дерево готово, НЕ закоммичено, передаётся на лендинг.** Заказ §4.1–§4.6 выполнен: конвенционные дефолты `mined_delta`/`mined_rejects` (ключей не объявляет и файлов заранее не создаёт никто) · глагол `tmctl bank-apply --config --decisions [--dry-run]` ($0, идемпотентен побайтово, всё-или-ничего, занятый flock — ровно 12 неблокирующе, ответ несёт версию и явное `depth: edit_wave`) · `--keys-file` у `translate` (строка **211**) · строгость сид-загрузчика (**212**) · `status_version` + `artifacts{}` с абсолютными путями (**213**) · дефолт стендового корпуса маркерным резолвом (**218**, критерий оркестратора выполнен буквально). **Вся доказательная база, замеры и попунктный чек промта — `backend/docs/SEAM_PACK_FINDINGS.md`;** здесь только то, что требует ДЕЙСТВИЯ. **⚠ ТРЕБУЕТ РАТИФИКАЦИИ: заведён класс полосы отказов 14 `decisions_rejected.`** Отвергнутый по существу набор решений ≠ сломанный деплой: `platform/internal/ingest/exit.go` `DeploymentFault` относит 10 к «условию, общему для всех книг хоста», и по 10 набор решений одного пользователя списался бы на оператора. Старый читатель деградирует безопасно (`Refused(14)` ⇒ `OutcomeRefused`). 10 сохранён для документа, не прочитавшегося КАК документ решений. Гейт тотальности таблицы добавлен. **⛔ ЗАКАЗ ПРИЁМКЕ — единственный незакрытый замер (согласован со сторонним архитектором).** Цепь целиком не гонялась ни разу: стоп красный → `bank-apply` → резюм → **стоп гаснет**. Части доказаны порознь, но мина №1 строки **199** — livelock уровня ЦЕПИ, которого частичные тесты по построению не видят. Снять не могу, и это проверено: банк-стоп на машине не воспроизводится (`contrast_path` → отсутствующий `eval/exp16/data/jieba_dict_general_zh.txt`, строка **123**), а до стопа надо дойти оплаченной черновой волной. **Команда:** на КОПИИ книги на exit 3 скормить `bank-apply` документ на всю карту подписи, затем `translate --verify-bank`. **ДЕФЕКТ** — стоп снова с ТЕМИ ЖЕ поверхностями; **не дефект** — свежая пачка ≤200 термов реверс-секции (мина №3 строки 199). До этого прогона вердикт по циклу — PLAUSIBLE. **⛔ ПИНГИ ЗОНАМ И СЛЕДУЮЩИМ ПАКАМ:** 1. **`rubyToCandidates` мёртв в проде** — единственный вызов из теста (`memseed_test.go:204`); `seeding.go:36-38` строит `manualSrcs` и никуда не передаёт (Go молчит: запись в map = использование). Строк `Source:"ruby"` в банке не возникает, а платформа держит для них ветку словаря (`ingest/bank.go` `termOrigin` → `OriginAnnotated`). 2. **Платформенному паку:** первый контакт с непере-мигрированной книгой даёт **exit 13** — воркер решений обязан планировать `13 → migrate → повтор` · **проекция берёт тот же лок**, поэтому правка термина при живом прогоне даёт 12 даже на превью, на всю длину прогона · **таймаут**: 1.6с на 5 000 термов банка, 9.1с на 20 000, бюджет платформы на вызов движка — 60с (`runs/reconcile.go:280`) · `bank-apply` создаёт `.lock` при первом касании никогда не запускавшегося проекта. 3. **⚠ БАТАРЕЯ НЕ ДЕТЕРМИНИРОВАННО ЗЕЛЕНА, и это НЕ пак — прочесть ДО пере-рана.** Два пред-существующих `-race`-флейка в `internal/store` срабатывают примерно раз на 2–4 полных прогона (`go test ./... -race -count=1` ×4 → 1 чист, 2-й красный). **Красное именно в этих двух тестах ожидаемо; любое другое красное — регрессия.** Тесты НЕ трогал (D39.121). `TestTheSeamIsNotChargedToTheStoreOperationBudget`, `TestKillMinus9LosesAtMostOneCall`. Чередующийся A/B на `git worktree` HEAD: оба падают на HEAD **не реже** (`kill9` 2/30 против 0/30; `seam` 2/30 против 1/30; без `-race` 0/30). Покрытие: `LockProject`/`Release` этими тестами не исполняются вовсе. ⚠ 0/30 против 2/30 НЕ значит «пак улучшает» — на таком числе это шум (Фишер p≈0.5); достаточно того, что флейк живёт на дереве, которого пак не касался. 4. **Гейт `TestDispatchCommandsCoversTheSwitch` слеп к `case` с константой** (считает только `*ast.BasicLit`) — команда, объявленная константой, невидима гейту, чей смысл в дефекте строки **176**. Я вернулся к литералу (конвенция файла), дыра не эксплуатируется. 5. **`redrive` — платный путь без канала ключей аргументом.** Промт: «флаг только у `translate`», сделано ровно так; канал у него есть (конвенционный `.env`), платформа его не зовёт. Развилка следующему паку. 6. **Однострочник при лендинге (не блокер):** документ решений читается неограниченным `os.ReadFile`, тогда как платформа свои чтения от движка капает. Документ на 10 000 решений ≈2МБ ⇒ кап 1–16МБ. **⚠ ПРАВИЛА, ВЫНЕСЕННЫЕ ИНЦИДЕНТАМИ — для следующих сессий, не про этот пак.** Прогон харнесса мутаций был убит таймаутом, `finally` не отработал, и посадка просидела в дереве ~40 минут, выглядя продакшн-кодом; сторонний ревьюер снял с него замеры и доложил несуществующий дефект. Поймал посадку собственный пин пака. Отсюда три правила: **харнесс мутаций обязан фингерпринтить цели** (починено) · **снимай отпечаток дерева перед сборкой бинаря замера** (на этом наступили обе стороны) · **одиночный прогон — не замер** (атрибуцию флейков я дважды вывел неверно из одиночных прогонов). **Что НЕ доказано:** рантайм-вердикт по платному пути — **PLAUSIBLE**. Доказано, что ключ из `--keys-file` доезжает до заголовка и что без флага прогон отказывает конфигом; не доказано, что реальный провайдер примет ключ и что SaaS положит файл по этому пути — платформенная половина строки **211**, чужая зона. Живой `translate` не гонялся: санкции на платные вызовы у пака нет. (24.08) **⛔ ПИНГ ОРКЕСТРАТОРА №19 — 23.08, ЗОНЕ БЭКЕНДА. Четыре движковых дефекта, все сверены моими командами.** 1. **ОТВЕТ НА ПИНГ ПОЛИГОНА: дефект ПОДТВЕРЖДЁН, он ваш.** Эскалация на Gemini теряет инъекцию банка памяти (строка **208**). Механизм пере-проверен: `pipeline/render.go:256-258`=`Role: "system", Content: injection, CacheBoundary: false` штатно строит ВТОРОЕ системное сообщение, а `llm/httpllm.go` (до-паковые строки 517-523, якорь ИСТОРИЧЕСКИЙ — сигнатура сменилась лендингом D39.164: функция приняла ось `mode SystemMessagesMode` и под квирком СКЛЕИВАЕТ системный ряд, так что описанного здесь «один-в-один» больше нет) копирует сообщения дословно — склейки нет нигде. ⚠ Лекарство того же класса в проекте УЖЕ написано и работает для другого провайдера: `llm/provider_anthropic.go:169-179`=`inSystemPrefix := true` сворачивает ведущий system-префикс в одно поле. Не обобщено на Gemini — это и есть работа. Полигон ждёт ответа с 22.08 и починил у себя по вендор-доке; их находка верна. 2. **Род не производится авто-путём вовсе** (строка **210**, дополнена 23.08): замка ДВА и они независимы, а поле `Gender` в схеме сида УЖЕ есть — ось мертва в ПИСАТЕЛЯХ, не в формате. 3. **Выпавший член юнита исчезает из отгружаемых байт БЕЗ внутритекстового маркера** — `pipeline/waverun.go:487-490`=`the flagged member is DROPPED from the edit`. Родня строки **193**; для читателя это молчаливая дыра в тексте. 4. **Загрузчик сида НЕстрогий** (строка **212**): голый разбор YAML без проверки имён ключей, и `seed-lint` этого не видит. Опечатка в имени поля молча теряется — в файлах, которые владелец правит руками чаще всего. ⚠ Плюс строки **211** (движок на SaaS не получает провайдерских ключей — блокер живого прогона) и **213** (платформа дублирует вашу конвенцию пути): обе половинные, движковая часть ваша. **⛔ ПИНГ ОРКЕСТРАТОРА №20 — 22.08, ЗОНЕ БЭКЕНДА: три ваших дока разошлись с деревом (аудит доков, сверено кодом; рукой не лезу — ваша зона).** 1. **`backend/README.md`, карта пакетов** объявлена сверенной по коду, а в ней НЕТ `internal/runevents` — эмиттера журнала прогона, то есть ВСЕГО шва движок↔платформа (D39.131), — и нет второго бинаря `cmd/tmvet`. Греп: `runevents` и `tmvet` в файле по нулю вхождений, при том что оба каталога в дереве. 2. **`backend/README.md`, «Известный техдолг»** держит живыми Cyrillic-хардкоды DC-чекеров, которые сам код называет ПОГАШЕННЫМ долгом (фаза 2 общности, D39.64: `isRuTarget` удалён, `TargetActive`/`TargetScriptNonLatin`), и оба его `file:line`-якоря указывают мимо. 4. **(дописано 24.08) `backend/README.md:47`=`/home/ubuntu/books/gu-zhenren/` зовёт книгу-стенд по МЁРТВОМУ пути** `/home/ubuntu/books/gu-zhenren/` — каталога нет ни там, ни в `$HOME`: книги переехали в `<репозиторий>/books` и версионируются своим git (D39.157 п.2). Там же фраза «тексты и производные ВНЕ git» стала неточной: вне ЭТОГО git. Тем же касанием — три тест-хелпера, резолвящие корпус от `$HOME/books` (строка 218); они уже в заказе движкового пака шва §4.6, так что дублировать правку не надо, но док обязан сойтись с ними. 3. **`backend/docs/components.puml`** — диаграмма, которую карта доков продаёт как правимую «одним коммитом с кодом», отстала примерно на месяц: старый exit-контракт (без 4/5 и полосы 10–19), неполный список глаголов CLI, удалённый паком-17 `failover.go` и ни следа эмиттера. ⚠ Рендерить её в svg/png НЕ надо (гардрейл) — править исходник. *(Пак «честные числа и статусы» — ИСПОЛНЕН, ПРИНЯТ С ФИКС-ЛИСТОМ и ЗАЛЕНДЕН `f840867` (17.08; D39.149, ФЧ-5 — D39.150). Отчёт, две волны адверсариального ревью, мутационная приёмка и тело фикс-листа ФЧ-1…ФЧ-8 — в [archive/PROGRESS-2026-08-17.md](archive/PROGRESS-2026-08-17.md); живой остаток — строка бэклога 197.)* *(Пинги закрытых паков — фикс-пак банка D39.118, блокеры контракта D39.122 — в [archive/PROGRESS-2026-08-04-09.md](archive/PROGRESS-2026-08-04-09.md), D39.125.)* *(Закрытые бэкенд-записи 01–02.08 — общность фаза 2 · качество банка (оба этапа) · фикс-пак банка · finding-1 · пакет-чекеров · мелкая пачка · coldrun-b · ручка эффорта — в [archive/PROGRESS-2026-08-01-02.md](archive/PROGRESS-2026-08-01-02.md).)* *(Закрытые бэкенд-записи 14–15.08 — эмиттер шва (D39.131) · `tmctl migrate` (D39.134) · пере-пин цен DeepSeek (D39.137) — в [archive/PROGRESS-2026-08-14-15.md](archive/PROGRESS-2026-08-14-15.md); вынесено D39.139.)* *(Закрытая хроника пакетов №0–№4 (04–10.07) перенесена в [archive/PROGRESS-2026-07-04-10.md](archive/PROGRESS-2026-07-04-10.md) — ревизия D31.)* *(Хроника пакетов 11–12.07 (пакет-5, D15.2-этап-А, санитайзер-фикс D33) — в [archive/PROGRESS-2026-07-10-13.md](archive/PROGRESS-2026-07-10-13.md).)* *(Трек-A пак-1/1.5 (D39.2/D39.5) — см. записи оркестратора №6 выше.)* *(Хроника бэкенд-паков 11–16 (19–25.07, вкл. отчёт-запись пака-15) — в [archive/PROGRESS-2026-07-13-25.md](archive/PROGRESS-2026-07-13-25.md); отчёты паков — `archive/reports/`.)* *(Закрытые записи 25–31.07 — пак-18 · мини-прогон · фикс-пак · сквозная проверка · хендофф 26.07 · ресёрч общности фаза 1 — в [archive/PROGRESS-2026-07-25-31.md](archive/PROGRESS-2026-07-25-31.md).)* ## ⟶ Вынесено 30.08 оркестратором №20: прожитые дампы паков «писатель книги» (D39.175) и «деньги» (D39.170) > Оба пака ПРИНЯТЫ и ЗАЛЕНДЖЕНЫ, их решения и числа живут телами нот. Здесь — исходные записки-планы и > итоги сессий целиком, как доказательная база. ⚠ Инструкции отсюда НЕ исполнять. ### 📝 ЗАПИСКА-ПЛАН — пак «писатель книги» (промт `docs/archive/prompts/BACKEND_BOOKWRITER_PACK_SESSION_PROMPT_2026-08-30.md`, строка 236), сессия `textmachine-main-fc`, 30.08 **Что беру (зона `backend/`, HEAD на старте `67c05be`, дерево чисто).** 1. Прод-пакет `backend/internal/bookfile` (только stdlib — `archive/zip`, `encoding/xml`; без `import "testing"`): `container.go` — OCF-контейнер (`mimetype` первым и `zip.Store`, `container.xml`, запись entry в порядке вызовов, нулевые таймметки) — `chunktest/epub.go` переводится на него, его OPF-заглушка и XHTML-декой остаются тестовыми; `epub.go` — EPUB 3 из нейтральной модели `Book{Identifier, Title, Language, Modified, Description, Chapters[]{Title, Paragraphs[]}}`: `dc:identifier`/`dc:title`/`dc:language`/`dcterms:modified`, nav (`properties="nav"`, ⛔ НЕ в spine), один XHTML на главу, `

` = заголовок главы, `

` = абзац; `txt.go` — чистый текст из той же модели (заголовок строкой, абзацы через пустую строку, главы через пустую строку; ни баннеров, ни флагов, ни `gapMarker`). 2. Сборка из `BookExport` — `backend/internal/pipeline/bookbuild.go`: `Runner.BuildBook(formats, partial)` → `Export(false)` → дыры → отказ либо файл. Запись `writeFileAtomic` рядом с ProjectDB (`.book.`, как `.manifest.json`/`.bank.json`), путь публикуется в `StatusArtifacts.BookFiles` (`book_files`: map формат→путь, аддитивно) — приор промта принят: платформа пути не выводит сама (17 §1). Существующий файл — перезапись: производный детерминированный артефакт, как манифест; бэкап отказывает потому, что он точка восстановления, а файл книги пересобирается из стора. 3. Аддитивные поля `tm-export-v1` (форма не меняется, все `omitempty`): `ChunkExport.Heading` (`heading`) — литерал `u.Members[0].Heading` в момент сборки записи (и у pending-юнита тоже, он из манифеста); `BookExport.GhostUnits` (`ghost_units`: `[{chapter,chunk_idx}]`) — ключи ghost-строк, чтобы маркер стоял в главе, а не только в счётчике; `BookExport.TextModified` (`text_modified`, `CCYY-MM-DDThh:mm:ssZ`) — max(`chunk_status.updated_at`) по финальным строкам, вошедшим в файл (колонка `NOT NULL DEFAULT datetime('now')`, `store/migrate.go:37`; в `ChunkStatus` дочитывается доп. полем). Это и есть `dcterms:modified`: последнее изменение отгруженного текста — факт стора, не часов процесса. 4. Глагол `tmctl build --config b.yaml [--format epub,txt] [--out path] [--partial]` (по умолчанию оба формата; `--out` — только при одном формате). Stdout — JSON-конверт `tm-build-v1` (пути, счётчики, `complete`). Новый глагол, а не флаг `export`: `export` — чистое чтение в stdout, писать файлы из него — смешать аудит-проекцию с артефактом; `manifest` — прецедент глагола-писателя сайдкара. `dispatchCommands` и usage-строка расширяются — контрактное расширение того же рода, что `bank-apply` (тест `TestParseNoArgsUsage` сам перечисляет такие расширения); чужим глаголам новые флаги ОТКАЗЫВАЮТСЯ (дисциплина `--keys-file`/`--max-units`). 5. Отказ — новый `pipeline.RefusalBookIncomplete = "book_incomplete"` → exit **16** (полоса 10–19; таблица тотальна по `TestTheRefusalTableIsTotal`); сообщение перечисляет дыры `глава/юнит/причина`. 6. Читательские слова — ДАННЫЕ: `//reader.txt` (`keyvalue`, плейсхолдеры `{chapter} {unit} {dropped} {holes} {total} {ghost}`), читает ОТДЕЛЬНЫЙ загрузчик `lang.LoadReaderWords`; в `Pack.Version()` НЕ фолдится (пин тестом «наличие reader.txt не меняет Version()» + `current_snapshot` minirun до/после). Ключ — язык ЦЕЛИ, не пара: слова читает читатель целевого языка, и они не зависят от исходного. Поставляю `backend/configs/langpacks/ru/reader.txt`. Каталог `/` загрузчик пака не читает (`Load` — только `/` и `/`, `langpack.go:215-330`), так что канал вне фолда по построению, а не по соглашению. **Политика §3.2 — по умолчанию ОТКАЗ, `--partial` — файл с пометкой; довод:** - Дыры: pending · withheld (`Disposition!="pending" && FinalText==""`) · incomplete (`DroppedMembers>0` при непустом тексте) — арифметика `render.go:573-587` выносится в общий хелпер `pipeline`, которым пользуется и `renderExport` (вывод `--plaintext` побайтово прежний) · **ghost — тоже дыра**: при укрупнении нарезки строка-лидер несёт текст ПРЕЖНЕГО, более короткого юнита, а остаток источника лежит в ghost-строках и в файл не попадает (acceptance: 57 строк → 37 юнитов, 20 ghost при `pending 0` — ровно «молча полная» книга). Позиция ghost внутри главы неизвестна → маркер в конце главы с числом потерянных строк. - Default = отказ: D29.1(б) — reader-facing выдача fail-closed; дверь платформы получит `--partial` для контрактного «may be exported, finished or not» (14 §createExport). - `--partial`: пометка неполноты — первый абзац первого документа spine (то, что читатель видит, открыв книгу) + `dc:description`; маркер — отдельный абзац на месте КАЖДОЙ дыры (pending/withheld — вместо текста юнита; incomplete — перед текстом юнита, как `gapMarker`; ghost — в конце главы). Форма — из `reader.txt`; без пака (acceptance, `runner.go:346`) — несловесная: `⚠ {chapter}.{unit}` у дыры, `⚠ {holes}/{total}` в пометке (символ + числа, никакого словаря в Go). В `FinalText`/`export.go` маркер не попадает. - drift — НЕ дыра: текст файла побайтово текст прогона (`export.go`, комментарий к `exportConfigDrift`); попадает в JSON-конверт `build` и в WARN, в файл — нет («config drift» — операторский словарь). Следствие §4 заявляю сразу: чистого бездрейфового файла на реальной книге на этом HEAD НЕТ — чистый путь доказывается синтетикой, реальные книги — посадки на дыры. - Оглавление и `

` при `Heading==""` — номер главы цифрами (пустая ссылка nav невалидна; цифры — единственный лейбл, который не решает открытый К-3 «какой лейбл видит читатель у главы без заголовка»; словесный шаблон в данных — 1 строка после ратификации, вопрос владельцу в отчёте). При `Heading!=""` — срез известного префикса `heading+"\n\n"` из `FinalText` (`chunker.go:186-192`), `

` = Heading. - Абзац = непустая строка `FinalText`. Замерено на `minirun/export.json`: юнит 1/0 — 14 разделителей `\n\n` и 7 одиночных `\n`, юнит 2/0 — 56 и 0: редактор чередует их без семантики, различать = выдумать различие; строка — единица абзаца исходника-вебновеллы. Круг §4 сравнивает абзацы ингеста с абзацами, которые положил писатель. - Метаданные: `dc:title` = `config.Book.Title` как есть (на стенде `蛊真人` — ограничение в отчёт), `dc:language` = `TargetLang`, `dc:identifier` = `urn:textmachine:book:`, `dc:creator` нет (источника нет). - 0 юнитов — отказ `sourceHasNoContent` (spine без элементов невалиден); 1 юнит — обычная книга. **Что НЕ беру:** платформу и дверь `createExport`/`getExport`; лестницу D25.1 в коде и в `BookExport`; строки 160/201 (`ApplyHeading` и три точки склейки не трогаю; модельные заголовки в прозе не извлекаю и не правлю); `--resnapshot` и любые платные вызовы; `langpack_root` в копию acceptance; направление письма (`dir`/`page-progression-direction`) — в `internal/lang` данных о направлении нет (`lang-script.txt` — только скрипты), RTL-цели в репо нет → вопрос в отчёт, не литерал; CSS/обложку; новые зависимости. **Чем докажу:** герметично — `bookfile` (mimetype первым/stored, nav не в spine, детерминизм двух сборок, экранирование, 0/1 глава), `pipeline` (круг `IngestEncoded` равенством абзацев на прогоне fake-провайдера; pending через потолок; withheld через санитайзер; **ghost — усадка исходника после прогона (посадки в тестах не было, строю)**; drift — бамп `prompt_version`; синтетический `BookExport` на incomplete), `cmd/tmctl` (incomplete бинарём через `setupGapProject`; парс флагов; exit 16 через totality-тест), `lang` (парс `reader.txt`; `Version()` не меняется от его наличия). На копиях книг в песочнице с переписанными путями: minirun — оракул июля 14/14 по `final_text/snapshot_id/disposition`, `current_snapshot` до/после `reader.txt`, `cmp` двух ПРОЦЕССОВ, epubcheck 5.3.0 `0 fatal / 0 error`, круг ингестом; acceptance — отказ с перечнем и `--partial`-файл с несловесными маркерами; coldrun-a — отказ 14/14 pending; `git -C books status --short -- gu-zhenren` до/после одинаков (снят до старта: 6 строк `D …-wal/-shm`). `make battery` + `-race`; в середине — субагент-ревью готовой части против критериев; в конце — адверсариальный проход по направлениям §4. ### ✅ ИТОГ — пак «писатель книги» (промт `docs/archive/prompts/BACKEND_BOOKWRITER_PACK_SESSION_PROMPT_2026-08-30.md`, строка 236), сессия `textmachine-main-fc`, 30.08 **Что построено (зона `backend/`, поверх `67c05be`; НЕ закоммичено — дерево на лендинг оркестратору; `git status`: 10 изменённых + 8 новых путей в `backend/`, плюс этот журнал).** - `internal/bookfile/` (новый прод-пакет, только stdlib, без `testing`): `container.go` — OCF-контейнер (`mimetype` первым, `Store`, через `CreateRaw` — без data-descriptor, флаги `0x0000`; `container.xml`; entry в порядке вызовов; нулевые zip-таймметки); `epub.go` — EPUB 3 (`dc:identifier`+`unique-identifier`, `dc:title`, `dc:language`, `dcterms:modified`, nav `properties="nav"` ⛔ не в spine, XHTML на главу: `

` + `

`); `txt.go` — чистый текст; `model.go` — модель `Book/Chapter`, `Blocks(i)` (единственное определение содержимого документа главы), `CleanText` (общая чистка XML-недопустимых символов для обоих форматов), экранирование. - `internal/chunk/chunktest/epub.go` переведён на `bookfile.NewContainer`; тест-декои (OPF-заглушка, `c