textmachine/docs/architecture/05-decisions-log.md

158 lines
44 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Журнал решений оркестратора — развязки перед Фазой 1 (2026-07-04)
Ответ на вопросы бэкенд-сессии (PROGRESS §«Вопросы от бэкенда», §«[НУЖНО РЕШЕНИЕ] перед Фазой 1») и полигона (think-режим). Каждое решение прошло адверсариальную панель из 3 критиков (research / экономика / исполнимость в коде Фазы 0); ни одно не отклонено, все уточнены. Это **контракт Фазы 1** — бэкенд исполняет отсюда; при конфликте с буквой 01-decisions/02-mvp-plan — источник истины здесь (потом сольём в основные доки).
Статусы: ✅ принято · ✅+ принято с уточнением панели.
---
## D12. Модель устойчивости к отказам (веха coverage-гейта, 04.07) — заземлена на durable-execution prior-art
4 вопроса бэкенда + большой вопрос владельца «падать ли / фолбечиться / как не терять книгу». Заземлено ресёрчем (Temporal / Step Functions / Airflow / River / Stripe idempotency / OpenRouter/LiteLLM fallback; проверено по первоисточникам).
**4 вопроса — все A:**
- **Q1 (сегментация): порт 1:1 наивного split.** `refusal_bench.py::split_sentences` = **источник истины** (он же приёмочный оракул); Go зеркалит бит-в-бит; любое изменение — Python-first, лок-степ. §3.7 «поглощение кавычек» → v1.1 (координированно). Иначе Go-гейт и Python-оракул разъедутся.
- **Q2 (эскалация): минимальный single-hop.** НЕ противоречит D4: D4 запрещал массовый outage-своп, а это точечная эскалация ОДНОГО чанка на детерминированном контент-провале. Нужна сейчас (DeepSeek-эхо воспроизводим). Минимально: 1 хоп → флаг; полные цепочки — шаг 7.
- **Q3 (детект): только excision_suspect (нижняя граница).** Верхняя аномалия-дописывание ложно флагает (ru-выход 1.41.9× исходника) — нужен entity-счёт+судья (Ф2); coverage_fail/D10 блокирован памятью v2.
- **Q4 (активация): opt-in (`enabled:false`), тестами.** Боевой флип — после валидации precision владельцем на реальных чанках (порог N флагов — за владельцем); до починки диалоговой сегментации флип ложно флагал бы диалого-плотные главы. **[exp07, коммит 3911baf]: precision гейта = 0 ложных/57 на реальных чанках; вердикт главы — ≥2 флага→fail, 1→attention; `finish_reason=content_filter` не эмитит ни один из 5 провайдеров (ветка эмпирически мертва). Флип `enabled:true` всё ещё за подписью владельца.**
**Таксономия отказа — ТРИ класса (не бинар «фолбэк vs пауза» — это мис-фрейминг):**
| Класс | Триггер | Реакция |
|---|---|---|
| Транзиентный / бюджетный | 429/5xx/timeout; `finish=length`/пустой | same-model retry (ось `attempt`, бо́льший max_tokens); ретраятся только `{length, empty}` |
| **Детерминированный контент-провал** | echo / cjk_artifact / excision / refusal — **HTTP 200!** детектит app-слой post-settle, НЕ транспорт | **single-hop escalation** на ДРУГУЮ модель (same-model retry бессмыслен); 1 хоп → флаг; отдельная ось модели в request_hash (не `attempt`); `escalation.budget_usd`; результат **ре-гейтится**; канал B изолирован ТИПОМ (permissive/nil-guard в failover.go), пишет новую TM-запись (model_id в ключе) |
| Outage / системный | 503-storm, 429-исчерпание, terminal-4xx, ceiling — **не per-chunk** | circuit-breaker (Azure Closed/Open/Half-Open, ~30с health-окно) → **пауза джобы + resume** (НЕ mass-swap: D4, стиль поплывёт + TM-инвалидация); супервизор рестартит `tmctl` |
**F3 фикс (оплаченный неидемпотентный вызов):**
- Provider idempotency-key = **существующий `request_hash`**, заморожен на входе (константа через транспорт-ретраи и resume), в заголовке `Idempotency-Key` где поддержано. ⚠ **Матрица (проверено по первоисточникам): OpenAI поддерживает; DeepSeek / xAI / Gemini / Kimi — НЕТ.** Центральный стек (draft-DeepSeek, editor-grok, judge-Gemini) provider-dedup не имеет → **не архитектурить F3 вокруг него.** Capability-флаг `supports_idempotency_key` в models.yaml (в готовый capability-слой).
- Для непокрытого стека — **at-most-once cap**: класс «billed-but-unconfirmed» (2xx с нечитаемым НЕусечённым телом / чистый timeout после отправки) получает бюджет **≤1 доп. вызова**, не `MaxAttempts1` (Temporal «maxAttempts=1 для non-idempotent»). Восстанавливает приёмку «≤1 вызов». + **консервативный settle оценки (не Release)** на post-send-timeout, чтобы потолок не был слеп (расширить существующий `BilledDecodeError`-путь); ledger-состояние AMBIGUOUS-BILLED. Честная модель — **«at-most-once billed, replay бесплатен»**, не exactly-once (saga/exactly-once на оплаченный вызов не мапятся).
**Consistency-aware fallback by stage:** draft-фолбэк свободно (editor ре-нормализует стиль — 2605.13368); **editor pinned per book** (на отказе editor — пауза / same-family, не чужая модель); model-per-chunk логировать для последующего uniformity-пасса. **Global call-budget guard:** суммарные попытки на чанк = `retries_on_primary + 1 fallback hop`, **НЕ сбрасывается на фолбэке** (иначе retry×fallback = 30+ оплаченных вызовов — LiteLLM #19985).
**Прогресс — единственный реальный пробел (ответ на «подсвечивать прогресс»):**
- `tmctl status --config book.yaml`**READ-ONLY** проекция `chunk_status`+`jobs`+`spend`+`request_log` (Temporal query-handler аналог; 0 LLM, 0 replay); `--json` (стабильные disposition/flag_reason enum как контракт) для CI/IDE. `chunk_status` бэкенд УЖЕ построил — read-model есть; нет только команды и знаменателя.
- **Book manifest на snapshot** (всего глав/чанков-на-главу) — иначе честный N/M невозможен. Вести **unit-счётом** (done/in-progress/flagged/skipped/pending), % только из N/M; **никакого синтетического time-бара** (fictional-bar анти-паттерн, Cloud Four).
- Словарь статусов (Airflow/Step-Functions): flagged≠failed (`jobs.status.failed` = infra-only), skipped с upstream-reason; **всегда REASON** (GitLab «explain why skipped»).
- **Паспорт качества главы** (JUnit `<testsuite>` + SonarQube gate): chunks total/ok/flagged/skipped, worst flag_reason, coverage-вердикт (pass|attention|fail по 5002500-char порогам), $, версия — против «проклятия 300-й главы» (04-unhappy §10).
- ETA от **сглаженного throughput** (EWMA α≈2/(n+1)), не present-state; unit-счёт ведущий, ETA вторично. Деньги в status: committed/reserved/ceiling%, F3-честный кумулятив, projected book cost = (cost/done-chunk)×remaining. Re-drivable флагнутые (Step Functions redrive), не трогать DispOK.
**Не строить (cautions верификатора):** distributed-движок (Temporal/Step Functions инфра — мы single-node/один файл, брать паттерны не инфру и не их HA-числа); прозрачный cross-provider fallback главного пути (D4); детектор контент-провала в транспорте (обязан быть app-слой post-settle — транспорт видит 200); real-time SSE-прогресс (Ф3, IDE вне MVP); наивный параллелизм чанков (гонка `Runner.clients` + «коммит терминов только на границе джоб»).
---
## Вторая волна развязок Фазы 1 (04.07, вопросы бэкенда после survey) — D8D11
Онбординг-поправки бэкенда **ратифицированы**: (1) дефолт-редактор grok-4.3 @ temp 0.4 thinking-OFF (не Kimi — «Kimi-editor 400» как повод для capability-слоя снят; живые 400 дают Gemini/gpt-5); (2) `deepseek-v4-pro` нигде не подтверждён → слаг канала A резолвить через `/models`, не хардкодить (deepseek-chat депрекейт **2026-07-24, ~3 недели** — миграция черновика срочная); (3) Gemini wire-слаг `gemini-3.1-pro-preview` (суффикс несущий, голый → 404); (4) models.yaml на диске отстаёт — бэкенд обновляет сам (его зона); (5) F4 (усечённый length течёт в edit как OK) чинить в A, F3 (retryable-but-paid переоплата) учесть явно. A/B/C-план бэкенда принят как есть.
### D8 (Q1) — memory-компонент snapshotID: **content-hash материализации** (не version-counter). ✅
Хэшировать детерминированно ORDER-BY-материализованное **полное состояние инъектируемой памяти на старте джобы** (approved-глоссарий + резюме + series-bible-lite — тот пул, из которого per-chunk селективная инъекция потом выбирает; сам per-chunk выбор детерминирован и уже покрыт request_hash через rendered messages). **Drift-proof — решающий довод:** version-counter (буква §3.2/D5.2) молча дрейфует, если забыли бампнуть, и переоткрывает ровно тот баг тихой переоплаты, который D5.2 закрывает. Content-hash забыть нельзя. Стоимость (хэш пула раз на джобу, не на чанк) — ничтожна, не горячий путь. Правит §3.2: memory-компонент снапшота = content-hash, не счётчик.
### D9 (Q2) — приёмочная книга Фазы 1 = **ja→ru ранобэ** (~150k токенов). ✅+
Причины: соответствует флагманскому «ранобэ»; COGS-запас (R5 ~$3.1 стандарт vs потолок); **ставит ruby/фуригану на критический путь импорта** — форсирует закрытие известной дыры (04-unhappy §4), иначе отложат и забудут. **Уточнение (границы ruby):** извлекать чтения фуриганы при первом появлении → **глоссарий-лок имени**, НЕ сохранять ruby в выходной вёрстке (это остаётся ограничением epub-v1, план:25). Хедж: если полное ruby-извлечение окажется тайм-синком, блокирующим веху — принять на ранобэ, где чтения имён капчатся из первого появления обычным путём, а полный ruby-лок — fast-follow внутри Фазы 1. CJK-echo (zh) тестируется CJK-гейтом отдельно (не требует zh-приёмочной книги). Все три пары строятся; коммерческая проверка пар — пилот Ф2.5.
### D10 (Q3) — знаменатель глоссарной консистентности = **ВСЕ approved-термины** (имена + выдуманные термины/титулы). ✅
Матчит букву R7 («гейт считает approved-термины») и — важнее — **бьёт по реальной жалобе №1 читателей**: коллапс терминосистемы рангов/титулов («Immortal, immortal or immortal», 04-unhappy §1) страшнее скачущих имён. Гейт параметризовать (знаменатель = вхождения src/алиасов, числитель = лемма dst по `decl`), **names-only — как под-метрика для диагностики**. Порог 98% — на детерминированном слое (pre-replace+инъекция+post-check) достижим (механизм не модель-зависим); если приёмка покажет систематическую недостижимость — ревизуем порог, НЕ знаменатель (не геймить метрику сужением).
### D11 (Q4) — премиум-микс: **конфиг-потолок, не гейт приёмки** ✅+ — но ОБА cost-числа устарели, не только премиум.
budget_usd = R5-clamp; дефолты **под селективный apex (пересчёт ВЫПОЛНЕН — [exp08](../experiments/08-cost-model-v2.md)):** ранобэ/роман ~$2, вебновелла — потолок-на-главу/`apex_fraction`; полный apex вебновеллы (~$82) не дефолт. Приёмка держится на резюмируемости + гейтах + **точности телеметрии денег**, а не на конкретном премиум-числе (exp01 намерял ~$49/$26 на Sonnet-апексе — заменено exp08 на Gemini-апексе с reasoning-as-output).
**Расширение за рамки вопроса бэкенда: порог стандарт-микса $0.6 ТОЖЕ устарел** — он считался с дешёвым редактором (GLM/DeepSeek), а дефолт-редактор теперь **grok-4.3 ($2.50/M output ≈ 10× дороже draft)**. Пересчёт ja→ru ранобэ: draft ~$0.065 + grok-editor ~$0.67 = **~$0.73 стандарт-микс** (выше $0.6). Экономика при этом здоровая (том <$1 против якоря $100 — маржа огромна), но **конкретные приёмочные числа ($0.6 стандарт, $5 премиум) оба устарели после смены редактора на grok**. → Приёмка Фазы 1 по стоимости = (а) escalation уважает конфигурируемый budget_usd (тест механизма); (б) телеметрия денег точна (ledger корректно биллит grok/gemini с их квирками — grok reasoning=0 при thinking-OFF через ЯВНЫЙ `reasoning_effort:none` — дефолт grok = low, gemini reasoning-as-output); мягкий sanity-check ~$0.69/ранобэ ([exp08](../experiments/08-cost-model-v2.md)), жёсткий гейт $0.6/$5 СНЯТ. Числа пересчитаны — exp08 (draft DeepSeek + editor grok + премиум Gemini).
### Ре-флаг (grok как центральная модель — усилилось с D8): гигиена аккаунтов xAI критична
grok-4.3 теперь дефолт-редактор → **каждый чанк каждой книги идёт через xAI**. Значит: (1) дефолт-редактор продакшна — **чистый xAI-аккаунт БЕЗ data-sharing** (промо-$150-аккаунт с data-sharing — только eval/NSFW, реальные книги через него нельзя, research/08); (2) концентрация трёх ролей (редактор+канал B+судья 18+) на xAI — риск доступности (Р10 №6): провайдер лёг → пауза/resume (D4), премиум-редактор gemini как валидированная замена под рукой.
---
## D1. Редактор C1 — монолингвальный. ✅+
**Решение:** редактор Фазы 1 видит только черновик + одобренный глоссарий (dst-термины как target-констрейнты), **не** исходник. Убрать `{{text}}` из `prompts/editor.md`.
**Обоснование:** издательская норма (редактор не знает язык оригинала — research/07); билингвальный редактор в zh→ru якорится к исходнику и плодит кальки (главная translationese-болезнь); монолингвальный редактор к тому же дешевле (исходник не сидит на входе каждого edit-вызова).
**Уточнения панели (обязательны):**
1. **Честно: coverage-гейт НЕ контролирует верность.** Он ловит только omission/дописывание по длине; same-length mistranslation (свап рода, «кто кого», неверная полисемия) проходит насквозь. Значит в Фазе 1 верность после черновика **не контролирует ничто** до билингвального судьи Фазы 2. Это допустимо (Фаза 1 — инфраструктурная веха), но записать прямо: **приёмка Фазы 1 не содержит критерия верности** — не маскировать это coverage-гейтом.
2. Пилот Ф2.5: плечо C1 (draft→editor) меряется на win-rate **только вместе с живым билингвальным судьёй** — иначе C1 по собственному признанию полирует неверное в «гладко-неверное».
3. **Код:** убрать `{{text}}` меняет SHA256 шаблона → resnapshot-гейт сработает громко (хорошо), но `prompt_version: "v0-draft"` в `pipeline-c1.yaml` станет врать (тот же лейбл на двух SHA). **Поднять edit `prompt_version` при этой правке.**
## D2. Диспозиция плохого чанка — skip+flag+continue. ✅+
**Решение:** `finish=length` → ретрай с бо́льшим max_tokens до капа, потом флаг; hard refusal/`content_filter` → флаг+продолжить; пустой оплаченный → skip+flag (не падение прогона); soft refusal (blacklist) → флаг. Флагнутый чанк не коммитит мусор/пустоту в TM.
**Уточнения панели (обязательны):**
1. **`attempt` этого НЕ несёт** (переоценка в моём ответе). В коде Фазы 0 нет ни цикла по чанкам (chapter=1/chunk=0 захардкожены, `runner.go:235`), ни хранилища флагов, ни статуса «skipped/flagged»; любой ненормальный путь сейчас = `SetJobStatus(failed)+return`. Механика skip+flag+continue — **net-new работа Фазы 1** (цикл + таблица флагов + колонка disposition, читаемая resume-путём). `attempt` — необходим, но не достаточен.
2. **Тегировать `flag_reason`.** `retry-flagged` переатакует **только** `length`/`empty` на той же модели; `content_filter`/refusal детерминирован per-модель/канал — повтор на том же SFW-канале снова откажет и снова спишет деньги. В Фазе 1 refusal-флаги просто **не переатаковать** (деньги на гарантированный отказ не жечь); маршрутизация в канал B/эскалацию — Фаза 2.
3. **Детектор вырождения перед удвоением max_tokens.** У дешёвого черновика `finish=length` чаще всего — repetition-loop; бо́льший max_tokens лишь оплачивает больше повтора (усугубляется неидемпотентными ретраями F3). Дешёвый n-gram-loop чек ПЕРЕД удвоением → при вырождении сразу флаг, без оплаты цикла.
4. **`content_filter` ненадёжен как ветка:** OpenAI-совместимый адаптер отдаёт `finish_reason` сырым (`httpllm.go`), пробрасывают ли его DeepSeek/GLM/Kimi — не проверено. Реальная страховка — **refusal-blacklist гейт**, а не ветка по finish_reason. Полигону: замерить, эмитят ли провайдеры `content_filter`. **[exp07: НЕ эмитит ни один из 5 — ветка эмпирически мертва, страховка = refusal-blacklist гейт.]**
5. **Edit `max_tokens` считать от длины ЧЕРНОВИКА, не исходника** (`runner.go:283-287` сейчас от source). Для монолингвального редактора (D1), чей вход/выход ≈ русский черновик (~1.9× токенов исходника zh→ru), оценка от source мис-сайзит бюджет и ложно триггерит length-ретрай.
## D3. Эскалация. ✅+
**Решение:** канал A — DeepSeek V4 Pro → GLM-5.1 → апекс Gemini 3.1 Pro; канал B (18+) — **Grok 4.3 основной пермиссивный тир** + DeepSeek офиц. + локальная abliterated. Sonnet удалён (Anthropic — дорого); OpenAI остаётся (ключ есть) — GPT-5 nano альт черновика, GPT-5 mini кандидат в редакторы/second-opinion судьи.
**Правка по xAI (04.07, вопрос владельца — xAI НЕ выведен):** ретайрнулся только дешёвый Grok 4.1 Fast; сам провайдер жив, ключ есть. grok-4.3 ($1.25/$2.50) — средний тир, не «дорогой крайний». Роли Grok: (а) **переводчик канала B** (самый пермиссивный крупный API — стандарт R-rated); (б) **судья/QA 18+ контента** — не отказывается оценивать explicit, в отличие от Gemini (нужен нативный safety-off) и Claude (отказ). Ограничения: reasoning xAI **аддитивный** → канал B с thinking OFF, а think-ON на Grok — только после reasoning-буфера в `EstimateUSD` (D6.2). Промо xAI **$150 за data-sharing**: брать на выделенный NSFW/eval-аккаунт для PD/тест-корпуса и refusal/18+-бенчмарка (разблокирует висящую 18+ часть эксп.02) — но **не гнать через data-sharing реальные/конфиденциальные клиентские книги** (конфликт с ToS-гарантией и zero-retention режимом B2B, research/08).
**Правка редактора (04.07, эксп.04 полигона — бейк-офф): дефолт-редактор Фазы 1 = `grok-4.3`.** Слепой бейк-офф glm-4.6 / kimi-k2.6 / gemini-3.1-pro / grok-4.3 на 4 фрагментах (2 фронтир-судьи, сверка с каноническими ru-переводами, согласие независимо): **grok-4.3** — ранг-1 верность, ранг-2 стиль, самый дешёвый+быстрый (~13с без reasoning) → лучший value «одной модели на роль». **gemini-3.1-pro** — лучшая проза → премиум-эскалация редактора. **GLM-4.6 слабейший** (опровергает допущение Р4 «редактор=GLM»; перемерить при GLM-5), **Kimi худший value** (~11k reasoning-ток./абзац, ранг-последний по верности) → оба сняты с дефолта редактора. Это **закрывает вопрос бэкенда №2** (дефолт редактора). Оговорка: LLM-судьи на коротких фрагментах — провизорный дефолт Фазы 1; **эксп.04 кормил редактор БИЛИНГВАЛЬНО (исходник+черновик), а боевой редактор D1 — МОНОЯЗЫЧНЫЙ (только черновик) → рейтинг grok на моноредактуре строго не перенесён (exp08 §Ограничения, Quality-transfer риск), проверяется в пилоте**; финальный выбор редактора/ядра — человеческий пилот Ф2.5. Р4 обновлён. Следствие: grok-4.3 стал центральной моделью (редактор SFW + канал B + судья 18+) — гигиена аккаунтов xAI на три использования (Р4), редактор с thinking OFF через ЯВНЫЙ `reasoning_effort:none` (дефолт grok = `low`; ledger чист).
**Уточнения панели (обязательны):**
1. **Часть моделей ОТСУТСТВУЕТ в конфиге (частично закрыто на 05.07).** Заведены с тех пор: `grok-4.3` + провайдер `xai` (commit 636919c); `deepseek-v4-pro` подтверждён живьём и по цене ($0.435/$0.87). Ещё НЕ заведены: провайдеры `gemini`/`openai`, модели `deepseek-v4-pro`/`glm-5.1`/`gemini-3.1-pro` в `models.yaml`; цепочки эскалации всё ещё `[kimi-k2.6]`-заглушки; редактор всё ещё `glm-5`. Задача бэкенда (см. промт консолидации): завести недостающие провайдеры+модели+цены и заменить заглушки цепочкой D3.
- **ПОПРАВКА (принял корректировку бэкенда): «без новых адаптеров» было оптимистично.** Провайдеры OpenAI-совместимы по эндпоинту, но не по телу запроса: текущий `openAIRequest` всегда шлёт `temperature` + `max_tokens`, а Kimi требует `temperature: 1` (иначе 400), gpt-5-mini — `max_completion_tokens` без temperature + `reasoning_effort: minimal`, Gemini 3.1 Pro — обязательный thinking (`thinking_budget: 0` → 400). Это **не строчка в yaml, а per-model capability-слой** в адаптере, и он нужен **раньше D3**: Kimi — уже альтернативный редактор Р4, edit-стадия на temp 0.4 даст 400 на первом вызове. → Capability-слой — фундаментальный элемент Фазы 1. Форма (согласована с принципом «модели/цены только в конфиге», Р4): каждая модель в `models.yaml` декларирует возможности — `budget_field: max_tokens|max_completion_tokens`, `temperature: send|omit|locked:1`, `reasoning_control: none|extra_body_disable|effort_minimal|mandatory`; адаптер строит тело запроса по ним. `experiments/00-provider-quirks.md` — эталонная таблица квирков.
2. **Премиум-бюджет $5/$30 УСТАРЕЛ** — выведен из калибровки на Sonnet $3/$15. Апекс теперь Gemini 3.1 Pro ($2/$12 ≤200k) с **обязательным thinking** (reasoning биллится как output $12/M — тот же класс недоучёта, что сжёг vojo 3044%). Плюс `batch 50%` к inline-эскалации неприменим (последовательна из-за STM), только к судье/QA. Плюс Gemini = и апекс, и судья → эскалированный чанк платит Gemini дважды (перевод+ре-джадж), оба с форсированным reasoning. **Полигону: пересчитать бюджет на фактическом Sonnet-free стеке с reasoning-as-output, batch только на судью.** До пересчёта — $-потолок конфигурируемый (D-эконом ниже), дефолты $5/$30 держим как консервативную заглушку.
## D4. Provider-fallback — НЕ добавлять для облачных draft/edit. ✅+
**Решение:** авто-кросс-провайдерного фоллбека для облачных ролей нет (фоллбек драфта = разный стиль/термины по чанкам + `model_id` в ключе TM обнулит хиты на ре-ране). Провайдер лёг → durable pause → resume. Локальный→облако failover из vojo — только для local-ролей, channel-aware. Ночь — супервизор перезапускает `tmctl`.
**Уточнения панели (обязательны):**
1. **Channel-aware изоляция в коде НЕ обеспечена** (дыра). `FailoverConfig` не имеет поля канала/пермиссивности — изоляция сейчас лишь doc-comment для вызывающего; сам failover в Фазе 0 не подключён. Хуже: ветка «fallback отсутствует → fail loud» **сделает nil-panic** (нет nil-guard, `c.fallback.Complete` разыменует nil), а путь «пустой local-контент → облако» — ровно тот сайлент-шип, о котором предупреждает заголовок, без guard. **При подключении failover (Фаза 2): изоляцию канала B делать типом (поле permissive/channel) или явной fail-loud веткой на nil-fallback, а не комментарием.**
2. Формулировка «resume бесплатен» переусилена: техдолг F3 — retryable-но-оплаченные вызовы не идемпотентны, resume может переплатить прерванный in-flight (до `MaxAttempts1`). Точнее: «resume переоплачивает максимум прерванный in-flight, всё равно кратно дешевле mixed-model failover».
3. Local→cloud failover для local-ролей канала A (экстракция глоссария, дешёвый судья-гейт) при OOM 8GB GPU молча превращает $0-вызовы в платные облачные — **un-budgeted; пометить** (телеметрия должна показывать, когда local-роль ушла в облако).
## D5. Онгоинг Analyst — инкрементально. ✅+
**Решение:** book-brief заморожен; новые главы — Analyst инкрементально расширяет резюме и ищет новые термины; approved-глоссарий/series-bible наследуются read-only, новые термины append, approved не ревизуются молча (research/07); разрешение gender=hidden-твиста — ручное событие.
**Уточнения панели (обязательны):**
1. **Проактивная эвристика `gender=hidden`.** Онгоинг структурно теряет преимущество «книга целиком видит твист заранее»: инкрементальный Analyst не видит ещё не загруженных глав, поздний реверс-трап (女扮男装) → уже вмороженные главы закоммитили род. Митигация: когда Analyst в **загруженных** главах видит намеренную неоднозначность (омофонное ta / pro-drop без дизамбигуации), ставить `gender=hidden` и гнать безродовые конструкции (гайд ООН) **до** раскрытия, а не дефолтить в мужской (TACL-биас). Плюс ретро-патч-протокол «с тома N — так» с показом стоимости пере-перевода вмороженных глав, которые твист фальсифицировал.
2. **КРИТИЧНО (impl): `snapshotID` не покрывает память и сборку контекста.** Сейчас хэширует только brief_hash + план стадий + defaults, **не** context-assembly (`glossary_injection`/`stm_depth`/`overlap`) и **не** инъектированный глоссарий/резюме/series-bible. Как только Фаза 1 начнёт инъектить память в `msgs`: изменённая память меняет `request_hash` (msgs в нём), но `snapshotID` не меняется → resnapshot-гейт **не сработает**ре-ран старой главы молча промахивается мимо чекпоинта и переоплачивает **расходящимся** переводом — ломается сам инвариант Р6, на который D5 опирается. **Свернуть memory-state + context-assembly hash в `snapshotID` ДО того, как приедет инъекция Фазы 1.** Это общий гейт Фазы 1 — держит и D1 (глоссарий редактору), и D5.
## D6. Think-режим — не глобальный дефолт, но пилотить на переводчике. ✅+ (существенно переписано)
**Решение:** `reasoning_effort` per-роль, дефолт **off/minimal** для draft/edit — но **по операционным причинам** (эмпирика полигона: GLM ×3 таймауты, Gemini жрёт max_tokens, gpt-5-mini выжигает бюджет — `00-provider-quirks`), НЕ потому что «reasoning вредит переводу».
**Переписанное обоснование (панель отозвала мою посылку):** прежняя «think ломает роль переводчика — пустой вывод» — **отозванная находка**: PROGRESS:230 исправляет её («пустота — артефакт тесного контекста, не зависание; с ctx 16k think завершается и реально чинит реалии: 羅生門→Радзёмон, чтение 朱雀»). DRT (research/03, arXiv:2412.17498) независимо показывает: long-CoT над метафорами/сравнениями **поднимает литперевод** — это роль ПЕРЕВОДЧИКА. Владелец был прав в гипотезе.
**Уточнения (обязательны):**
1. **Пилот Ф2.5 think-ON vs OFF по качеству включает draft/translator и editor** (не только Terminologist+эскалацию, как я ошибочно сузил) — именно туда указывают и полигон, и DRT. Дефолт-off остаётся до результатов пилота.
2. **Скоуп think-ON эскалации — только subset-billing провайдеры** (deepseek/zai/kimi, где reasoning ⊆ completion ≤ maxTokens, `EstimateUSD` покрывает). **xAI/Grok исключить из think-ON** пока `EstimateUSD` не резервирует явный reasoning-бюджет (additive reasoning xAI → «потолок пробьётся» ровно на премиум-пути канала B).
3. **Gemini-апекс/судья — форсированный think-ON** (не чтит `reasoning='off'` → HTTP 400). Заложить его в бюджет как reasoning-as-output ($12/M), а не как opt-in. `runner.go` шлёт `st.Reasoning` — адаптеру Gemini `off` придётся молча глотать; этот кейс назвать явно.
4. «×23 COGS» занижает многословных ризонеров: Kimi-k2.6 ~11k reasoning-ток./абзац = ×4…×20 на output этой роли — учесть в бюджете эскалации.
---
## D7. Схема банка памяти v1 — скоуп. ✅+
**Решение (подтверждаю рекомендацию бэкенда с двумя правками):**
- **v1 (store v2-миграция):** база Р3 (`src, dst, type, aliases, gender, speech, decl, since_ch/until_ch, status, note`) + **alias-граф** (типы: имя/цзы/хао/прозвище/титул) + `gender=hidden(until_ch)` + `translit_policy` (западные имена через катакану — `04-unhappy §9`) + `first_person` + **series-bible-lite = gender + ты/вы**.
- **Правка 1 (панель D5):** ты/вы в series-bible-lite — **журнал событий переходов**, а не статичная матрица пар (переход ты↔вы — сюжетное событие, `12-ru-target §2`). Поле `gender`с проактивной установкой `hidden` Analyst-ом на неоднозначности (D5.1).
- **Правка 2:** `nickname_translation` **оставить в v1** (это одна колонка + лок «перевести раз навсегда», `04-unhappy §1` относит в Фазу 1); дорогая только логика первичного перевода сильной моделью — её можно отложить, но поле держать.
- **Отложить в v1.1:** `ranked-set` (инъективность ступеней), словарь эвфемизмов, число-словарь мер/разрядов (сам число-**гейт** — Фаза 1, а словарь конвертаций — v1.1).
- **Обязательно:** сверка с «Следствиями для плана» `04-unhappy-paths`; и `snapshotID` покрывает состояние памяти (D5.2) — иначе схема памяти без хеша в снапшоте молча ломает resume.
## Q3/Q4 бэкенда — прямые ответы
- **Q3 (эскалационный дефолт RU-контура):** см. D3 — не-Anthropic, дёшево-первым (DeepSeek Pro/GLM-5.1), апекс Gemini; Sonnet не дефолт (удалён). В будущем зарубежном контуре премиум-эскалация опциональна, но не сейчас.
- **Q4 (per-role provider-fallback):** см. D4 — **нет** для облачных draft/edit; pause/resume; local→cloud только для local-ролей, channel-aware типом.
## Требования Фазы 1, не покрытые 6 решениями (из «missed» панели) — в скоуп
1. **Runner без цикла по чанкам/главам — главный длинный шест** (chapter/chunk захардкожены). Цикл + хранилище флагов + disposition-колонка — фундамент, на который опираются D2, D5, гейты, эскалация. Строить первым.
2. **`snapshotID` расширить** memory-state + context-assembly hash ДО инъекции (см. D5.2) — общий гейт.
3. **ruby/фуригана-парсер в спеку импорта Фазы 1** (детерминированная часть, `04-unhappy-paths §4`). Противоречие с планом:25 («epub v1 фуригану не сохраняет») разрешить: для ja→ru фуригана несёт авторские чтения имён → глоссарий-лок; молчаливая потеря подрывает приёмку ≥98% консистентности ja-книг. Извлекать чтения на инжесте (не сохранять инлайн-разметку, а **выхватить ruby в глоссарий**).
4. **Дешёвые детерминированные гейты Фазы 1** (были в `04-unhappy-paths §6/§9`, но выпали из плана): линтер тире-диалогов (Розенталь §4752), ёфикатор, блок-лист транслит-междометий, число-гейт разрядов 万/億. Самый дешёвый и заметный читателю класс — «максимум воспринимаемого качества за минимум денег». Морфозависимые (канцелярит) остаются Фазой 2 (Python-сайдкар); эти четыре — детерминированные, Go, Фаза 1.
5. **Экономика инвалидации TM** (`[НУЖНО РЕШЕНИЕ] п.2в`): смена параметров чанкера = полная инвалидация «как смена brief» с показом стоимости — **да, подтверждаю** (ключ TM от src-чанка, перечанкование by design инвалидирует; пользователю показывать $ пере-перевода). prompt_version и model_id в ключе (уже зафиксировано Р6) означают: правка промпта обнуляет TM-библиотеку, эскалация пишет новую запись — это осознанная плата за корректность, не баг.
## Экономические открытые пункты (полигону, гейтят достоверность бюджета)
- Пересчёт премиум-бюджета на Sonnet-free стеке с reasoning-as-output (D3.2), апекс Gemini 3.1 Pro (форсированный thinking).
- ~~Проброс cache-hit/batch RU-агрегаторами~~ — **СНЯТО** (коррекция владельца 04.07: EU-SaaS, прямые ключи, BYOK отменён; см. Р5). Вся cache/batch-экономика теперь по прямым ключам без оговорок — это канон.
- `budget_usd: 0` в `pipeline-c1.yaml` — заглушка; $-потолок владелец задаёт по ценовому намерению, дефолты $5/$30 держим до пересчёта.