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

218 lines
64 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) + пост-ревью D13D17 (2026-07-09)
Ответ на вопросы бэкенд-сессии (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 держим до пересчёта.
---
# Пост-ревью решения D13D17 (2026-07-09, оркестратор)
Ратификация выводов стратегического ревью [`07-strategic-review.md`](07-strategic-review.md) (метод: 14 сборщиков + 9 адверсариальных верификаторов, 8 CONFIRMED / 1 PARTIAL). Владелец подтвердил курс 09.07 (вкл. «Rust — нет, не сейчас»: Р1/Go остаётся; вопрос закрыт до пост-пилота и требует конкретного триггера, где Go упирается). Каждое решение ниже — контракт для следующих сессий «Бэкенд»/«Полигон»; хендофф-промты выдаёт оркестратор.
## D13. Пилот Ф2.5 — поправки протокола (гейтят валидность главного решения). ✅
Дополнения к `experiments/09-pilot-protocol.md` (полигон вносит в док и харнесс ДО пре-регистрации):
1. **Новый арм: «моно vs билингв редактор на ОДНОЙ модели, простой general-промпт»** — прямой тест D1. Основание (верифицировано по PDF arXiv:2605.13368, вкл. en→ru): monolingual-рефайнмент теряет accuracy с ПЕРВОГО прохода (2.2…5.6 MQM у всех 6 моделей), general-рефайнмент С исходником даёт тот же fluency при accuracy ~flat и лучшем overall. Существующий 7-bis (моно-бейкофф МОДЕЛЕЙ) сохраняется — это другой вопрос.
2. **C2 — только с ГЕТЕРОГЕННЫМИ кандидатами** (draft-кандидаты от разных моделей, не N сэмплов одной): на гомогенных селекция ≈ монета (2603.20324: WR 0.512 homogeneous; LitMT 2606.05924: best-of-8+судья — последнее место). Иначе плечо C2 предрешено и жжёт бюджет впустую.
3. **Панель судей — переконфигурация**: (а) 23 семейства максимум — 9 судей ≈ 2 эффективных голоса (2605.29800), лучший одиночный ≥ панели; (б) **11+ повторов на вердикт со свапом позиций A/B** — одиночный прогон нестабилен (13.6% флипов, парафраз меняет исход в 25%; 2606.13685); (в) агрегация медианой/Bradley-Terry (JP-TL-Bench-паттерн), не средним; (г) **grok исключён из судейства grok-редактированных выходов** (нарушение собственного анти-self-preference правила §5); (д) валидация судьи — по κ против человеческого IAA + tie-rate/top-1 within-prompt (2603.12520), не по средней корреляции.
4. **Пре-регистрация расширена**: MDE/мощность (число BWS-наборов), N аннотаторов ≥3 (при «минимум владелец» κ неисчислим — тогда честно писать «безκ-режим»), развести имена: C0C3 = конфигурации ядра, **M0M3 = режимы терминологии memory-eval** (коллизия имён в exp09 устранена).
5. **Харнесс чинится ДО решающего прогона** (верифицированные дефекты): memory_eval — стрип/запрет эха глоссарий-преамбулы, сохранение ПОЛНЫХ выходов (не out[:160]), реализация fidelity-оси (без неё правило «M3 роняет vs M0» непроверяемо; текущий индикатив ЗАНИЖАЕТ вред неверной инъекции — chunk1-C3 надут эхом); оценочные артефакты — пере-рандомизация меток по фрагментам, ключ/цены ВНЕ артефакта (слепота exp04 была структурно сломана: ключ лежал в том же документе при LLM-оценке). Инструменты: Pearmut (2601.02933) вместо самописного BWS-тулинга — оценить перед постройкой своего.
6. **Think-арм grok-редактора требует reasoning-буфера в `EstimateUSD`** (D6.2) — иначе экономически главный think-вопрос пилот не ответит; бэкенду заложить до прогона.
## D14. Канал B (18+) — ревизия состава. ✅
1. **DeepSeek ВЫВЕДЕН из explicit-части канала B**: ToS DeepSeek (upd 2026-03-27) §3.4(5) прямо запрещает «pornographic, obscene, or sexually explicit (e.g., sexual chatbots)» — верифицировано по первоисточнику. Для violence-фрагментов (не sexually explicit) DeepSeek остаётся допустим. Интерим D3 «DeepSeek офиц. + локальная abliterated» сужается до «локальная abliterated + Grok».
2. **Mistral — кандидат-фолбэк канала B**: Usage Policy (eff. 2026-06-11) не содержит бланкетного запрета adult-текста (только CSAM/NCII/эксплуатация) — верифицировано по первоисточнику. Полигону: прогнать refusal/excision-пробу Mistral на explicit-корпусе, бэкенду — слот в конфиге при положительной пробе.
3. **Обоснование xAI поправлено**: «цитата Маска про R-rated текст» — на деле про Grok Imagine (картинки); опора — сам AUP xAI (нет бланкетного запрета adult-текста; запрещены CSAM и порно реальных лиц). Позиция стабильна на 09.07, но политика xAI волатильна — мониторинг остаётся.
4. **18+ эмпирика — единственный critical ревью**: refusal/excision на explicit-корпусе (Grok/Mistral/abliterated) + качество Grok-судьи 18+ + поведение Gemini-судьи на explicit — обязательная рука exp02/пилота. Гейтится фрагментами от владельца.
## D15. Resume-экономика и онгоинг. ✅
Верифицировано исполнением: `snapshotID` вшит в `RequestHash` → любой `--resnapshot` (флип гейта, append approved-терминов, бамп версии классификатора) переоплачивает ВСЮ книгу, включая байт-идентичные запросы; redrive флагнутых нет. Решения:
1. **Текущий all-or-nothing ПРИЕМЛЕМ для статичной приёмочной книги Ф1** (гейт громкий, расхождений не бывает) — НЕ чинить до приёмки.
2. **Онгоинг-режим (`add-chapters`, сегмент «ИИ-фабрик») ГЕЙТИТСЯ** одним из двух: content-addressed переиспользование чекпоинтов (`msgsContentHash` уже существует — `render.go:233-249`) ИЛИ селективный redrive. Бэкенду: спека-дизайн (не код) в ближайший пакет; реализация — Ф1.5.
3. **Немедленно (в ближайший бэкенд-пакет)**: исправить лгущие комментарии `runner.go:156-160` / `coverage.go:29-33` («re-classify for FREE» верно только внутри неизменного снапшота); `tmctl status` + redrive флагнутых — приоритет D12-хвостов подтверждён (status → redrive → F3); бампнуть edit `prompt_version` (D1-уточнение №3 не исполнено — один лейбл `v0-draft` на два SHA).
## D16. Банк памяти — фиксы границ доверия (4 верифицированных дыры). ✅
Ядро (F1/нормализация/спойлер/детерминизм/инъекция) ревью подтвердило чистым; все находки — на границах доверия, каждая воспроизведена временным Go-тестом:
1. **Полисемия same-src**: две approved-записи (один src, разные sense/dst, пересекающиеся окна) инжектятся ОБЕ как CONFIRMED + post-check даёт гарантированный confirmed-miss (с гейтом ON — детерминированный livelock чанка). Фикс: fail-loud на пересекающихся окнах разных sense в `loadGlossarySeed` ИЛИ даунгрейд обоих совпадений в AMBIGUOUS. (`memory.go:198-204,299-306`; `memseed.go:155-163`)
2. **Sticky-апгрейд**: collision-prone AMBIGUOUS попадает в activeIDs без disposition (`memory.go:323`) → в следующем чанке sticky даёт status-based CONFIRMED, минуя post-check, прямо в редакторские констрейнты. Фикс: наследовать disposition исходного матча в sticky-кэрри (или не класть не-CONFIRMED в activeIDs). ⚠ Это был **ложно-негативный отсев 44-агентного ревью (0/3)** — рациональ «sticky несёт установленный в сцене термин» не проверялась на коллизионном входе; бэкенду выборочно перепроверить остальные отсевы того ревью.
3. **Source-граница для фонетических ключей ≥4**: `rose` (approved) фаерится CONFIRMED внутри `roseanne` — collision-даунгрейд покрывает только ≤3, Aho-Corasick границ не знает, target-сторона границу имеет (асимметрия). Фикс: script/word-граница на source-стороне для фонетических скриптов.
4. **Ruby-чтение не матчится**: для ручных терминов чтение выбрасывается (`memseed.go:217-220`), у auto-кандидатов surfaces строятся только при непустом dst → ja-чанк с именем каной даёт 0 матчей. До реализации B6: ruby-reading → auto-alias ручного термина; интерим — **обязательный пункт чек-листа подготовки ja-книги: kana-написания сид-имён вносить als aliases руками**.
Плюс: kana-precision замер (minKeyLenPhonetic=3 — «консервативный дефолт до замера») — полигону, расширение E1-протокола; C4-автозаполнение `decl` остаётся Ф2, но признаётся условием реалистичности жёсткого postcheck-гейта.
## D17. D1 (монолингвальный редактор) — статус понижен до «дефолт Ф1, оспорен внешним замером». ✅
D1 остаётся дефолтом Фазы 1 (менять конфигурацию до пилота не будем — kalki-риск не опровергнут, а вакуум верности Ф1 уже признан D1.1). Но: (а) сильная форма обоснования («исходник в контексте редактора ⇒ кальки») понижена до гипотезы — подтверждений в литературе 202526 нет, кальки документированы как болезнь ДРАФТА; (б) экономический довод слаб (билингв-редактор = +1520% стандарт-микса ≈ +$0.100.14/ранобэ); (в) решает пилот-арм D13.1. Если арм покажет паритет верности при моно — D1 подтверждается дёшево; если налог на верность воспроизведётся — флип на «редактор с исходником, простой промпт» дешевле любой альтернативной митигции.
## Ратификации по итогам внешнего ревью двух пакетов (09.07 вечер, оркестратор)
Оба пакета (бэкенд D15/D16, полигон D13-харнесс) прошли внешнее ревью оркестратора (7 ревьюеров, всё исполнением: моки, мутационные реверты, CLI-прогоны $0, пересчёт данных). Вердикты: **оба ACCEPT_WITH_FIXES, залендены** (153277e, 9afea37) — блокеров на дефолтном конфиге нет, деньги/корректность целы. Решения:
1. **D16.3-дивергенция бэкенда (kana/Han без source-границы) — РАТИФИЦИРОВАНА.** Кана-граница рушит recall (すずきは-класс: TP 15→1 — независимая эмпирика полигона, exp09 §6-bis), homograph-класс длино-инвариантен и границей не лечится. Митигация kana-омографов — POS/known-word-гейтинг или ruby-якорь → **скоуп B6** (вместе с Палладий/Поливанов-валидатором). **MIN=3 для фонетических ключей ратифицирован как дефолт** (kana-замер полигона; precision там trap-set-относительная — не продовая метрика).
2. **Спека D15.2 — направление ратифицировано, реализация ЗАБЛОКИРОВАНА до доработки** (3 дизайн-дыры ревью): (а) fast-path-гард обязан покрывать ВЕСЬ wire stage-план (текущий драфт теряет model/temp/reasoning/maxTokens-деривацию → смена temperature или reasoning low→none тихо отдаёт stale-текст — ре-открытие F1); (б) расщепление memory_version (wire vs verdict) — ОБЯЗАТЕЛЬНО, не «опционально» (иначе append-термина/toggle гейта = переоплата книги, главный сценарий не материализуется; подсказка ревью: postcheck пересчитывается вживую и в chunk_status не хранится — memory_version можно целиком убрать из ключа вызова); (в) замена loud-гейта согласия — pre-flight dry-run «N чанков, ~$X» с порогом явного флага (Р6-класс согласия на трату не демонтируется без замены). Плюс: полевой маппинг всего снапшота (wire/verdict/dropped), судьба chunk_status.snapshot_id/status-ридеров, sequencing миграции ДО первой онгоинг-книги, blast-radius append-а моделировать со sticky+eviction+каскадом стадий.
3. **D14.2 Mistral: refusal-часть ПРИНЯТА** (11/11 ok, провенанс на месте), **fidelity-числа (8595) НЕ приняты** — сырых выходов и судейских вердиктов в репо нет (провенанс-обещание не выполнено); пере-прогнать с персистом до финализации D14.2. Explicit-часть — ждёт фрагменты владельца.
4. **Pearmut — отказ ратифицирован** (нет BWS); инструмент пилота = тонкий свой + Potato-фронт + attention-checks.
5. **Новые задачи бэкенду (следующий пакет):** (а) GB18030-детект/конверсия в ingest (реальные zh .txt — не UTF-8; укусит на первой живой книге); (б) **homophone first-wins** — major ревью: при двух ручных омофонах (разные dst, чтение ≥4 каны) алфавитно-первый получает авторитетный kana-алиас CONFIRMED (attachRubyAliasesToManual видит коллизию только вторым проходом; группировать чтения по нормализованному ключу МЕЖДУ базами до аттача); (в) закрепить мутационно инвариант redrive «DispOK-стадии не сбрасываются» (тест с флагом на ПОСЛЕДНЕЙ стадии; сейчас мутант выживает) + тест --reason-селектора; (г) snapshot-чек ДО ResetChunkStages (сейчас громкий отказ redrive уничтожает флаг-телеметрию); (д) minors: --json exit-код при флагах, developer-подсказка для gate-промоченного glossary_miss (redrive-тупик), README «миграции v1v6», ETA→EWMA или пометка отступления.
6. **Новые задачи полигону (следующий пакет):** (а) зеркало ↔ Go: дозеркалить suppressUnboundedPhonetic (D16.3) и апостроф-фолд memnorm-v3 (оба продемонстрированы расхождением; критично для en→ru руки пилота); (б) эхо-детектор — ловить хвостовой/встроенный эхо-глоссарий, не только преамбулу; (в) Mistral-переснятие с персистом (п.3); (г) байт-синк заголовка инъекции с Go (⟨проверить⟩-пояснение); (д) minors: ts в refusal-записи, «508→500» trad2simp-дедуп/пометка, in-file карантин старых C-меточных данных.
7. **Гигиена:** `*.key.json` добавлен в .gitignore (остаточный риск утечки ключа слепоты); правило git-координации мультисессий внесено в CLAUDE.md (инцидент: перезапись истории оркестратором при грязном дереве полигона — недопустимо без сверки чужих правок).
## Сопутствующие правки доков (оркестратор, 09.07)
`02-mvp-plan.md` → v3 (мёртвые пороги/интерим-18+/фазовые списки синхронизированы с D-логом); обе `*.puml` → v3 (убраны Opus/BYOK/«≤2×», дорисованы ре-гейт эскалации, три класса отказов D12, человек в петле, фазовые пометки); superseded-баннеры на `research/01, 02, 03, 04, 09`; провенанс-шапки на `research/12-architecture-as-antipattern.md` и `12-common-failures.md` (происхождение не зафиксировано — до уточнения владельцем числа оттуда не абсорбировать как факты); закрытые промты → `docs/archive/prompts/`; онбординг: корневой `CLAUDE.md` + `backend/README.md` + `eval/README.md`.