# Заметки реализации (сессия «Бэкенд», 2026-07-04)

Вердикт шага 0 — перевалидации решений v2 глазами реализатора перед Фазой 0.
Метод: собственное чтение донорского кода vojo + воркфлоу из 4 критиков (линзы: консистентность доков,
исполнимость в Go, сверка с донором построчно, онлайн-фактчек цен/API; ~940k токенов, находки ниже отобраны и объединены).
Формат: **подтверждаю** / **правки реализации** (фиксируются здесь, архитектурные доки не трогаю) /
**[НУЖНО РЕШЕНИЕ]** (продублировано в PROGRESS; с ревизии D31 — в `../archive/PROGRESS-2026-07-04-10.md`; все пункты развязаны D-логом).

## 1. Общий вердикт

Архитектура v2 **исполнима, кодить можно**. Противоречий, блокирующих Фазу 0, нет. Подтверждаю:

- Р1 (Go + порт vojo): донор сверен построчно — llm.go/httpllm.go/failover.go/pricing.go/telemetry.go/store.go
  существуют, объёмы и механизмы соответствуют research/10 (retry-классификация 429/5xx vs терминальный 4xx,
  типизированная `httpStatusError`, reserve/settle с TOCTOU-защитой, анти-flapping брейкер, «2xx с пустым
  контентом = оплаченный вызов», priceFor никогда $0, 137 тест-функций).
- Р3/Р6 (SQLite, modernc.org/sqlite): FTS5 и WAL в pure-Go драйвере есть; чанк-чекпоинты и kill -9-тест
  реализуемы (одна маленькая транзакция на LLM-вызов при вызовах в 10–300 с — тривиальная нагрузка).
- Р5 (cache-раскладка): схема «стабильный префикс → волатильный хвост» совместима одновременно с Anthropic
  `cache_control` (явные брейкпоинты, max 4) и DeepSeek-автокэшем (prefix-match с 0-го токена, блоки по 64
  токена) — селективный глоссарий в хвосте кэш DeepSeek не ломает.
- Конфликты, разрешённые ревью v2 (кэш↔селективная инъекция, modernc↔sqlite-vec, интерим 18+, чанк-чекпоинты),
  действительно согласованы по всей цепочке доков.

## 2. Упущения передавшей сессии, найденные валидацией (главное)

1. **В `Usage` донора нет cache-write** — у Anthropic запись в кэш платная (×1.25 за 5m TTL / ×2 за 1h),
   поле `usage.cache_creation_input_tokens`. Без него — систематический недоучёт расходов, тот же класс
   ошибки, что уже оплаченный в vojo недоучёт reasoning-токенов 30–44%. research/10 это предлагал (§3 п.1),
   но в обязательства Фазы 0 в плане не попало. → В `Usage` добавляю `CacheCreationTokens`, в `ModelPrice` —
   `CacheWritePerM`; формула: `(prompt−cached)·in + cached·cacheRead + cacheCreation·cacheWrite + (completion+reasoning)·out`.
2. **Скрытый третий таймаут донора**: полигон знал про 45 с (нога failover) — но в транспорте httpllm.go:214
   захардкожен ещё и per-attempt 60 с, а maxTry=3. С чанками 63–278 с любая генерация >60 с была бы убита и
   трижды бесплатно ретраена. → Все таймауты/ретраи — параметры клиента из конфига (профиль per-провайдер
   per-роль), + чтение `Retry-After` на 429 (донор его игнорирует, backoff капится 8 с — молоток по
   rate-limit'ам массового прогона).
3. **Прокси-ловушка глубже, чем в памяти проекта**: Go stdlib НЕ проксирует loopback никогда, так что
   127.0.0.1 сам по себе безопасен; опасны **не-loopback локальные адреса** (WSL-gateway 172.x, как в самом
   vojo). Все клиенты донора — `&http.Client{}` с `ProxyFromEnvironment`. → В адаптере локального провайдера
   и его пробах явный `Transport{Proxy: nil}`.
4. **Grok 4.1 Fast мёртв** (retired 15.05.2026, слаги молча редиректятся на grok-4.3 c ДРУГОЙ ценой
   $1.25/$2.50) — таблица Р4 в этой части устарела ещё до старта кода. Состав/цены — только configs/models.yaml
   (Р4 это и предписывает), но канал B остаётся без «дешёвого Grok» — see [НУЖНО РЕШЕНИЕ].
5. **store.go — переписывание, а не «адаптация <20%»**: advisory-локов в SQLite нет, весь файл на pgx-API,
   диалект Postgres (IDENTITY, JSONB, GREATEST, now()). Переносится семантика (reserve/settle/release,
   миграции per-step, идемпотентные шаги, тесты как поведенческий контракт), не код. Оценку research/10
   по этому файлу считать оптимистичной; трудоёмкость Фазы 0 это не ломает (семантика простая).
6. **Приёмка Фазы 0 требует больше, чем список работ Фазы 0**: `tmctl translate` на один чанк draft→edit
   подразумевает мини-раннер, промпты Translator/Editor и сборку контекста. → Мини-раннер C1 — явный
   деливерабл Фазы 0 (здесь зафиксирован).

## 3. Контракты Фазы 0 (обязательства реализации)

### 3.1 Детерминизм и request-hash (Р6)

Рендер промпта — **чистая функция** от `(snapshot джобы, закоммиченные чекпоинты предыдущих чанков, индекс
чанка)`. Запрещены timestamps/UUID в промпте; все выборки из памяти — с `ORDER BY`; все map сортируются по
ключу перед рендером. Проверяется unit-тестом «два рендера байт-в-байт идентичны».

`request_hash = sha256(book_id | chapter | chunk_index | stage | role | model+sampling | snapshot_id | sha256(rendered_messages))`.

Противоречие буквы Р6 («ключ: request-hash» vs «волатильные части исключаются») разрешаю так: хэш считается
от запроса, **отрендеренного из snapshot**, — волатильность заморожена снапшотом, исключать нечего.

### 3.2 Snapshot контекста джобы

Материализованная запись с собственным `snapshot_id`: `brief_hash`, версии промпт-шаблонов per-role,
model+sampling per-role, версия чанкера, зафиксированное состояние approved-глоссария (версия-счётчик),
style sheet, резюме книги/арок на момент старта джобы. **STM в snapshot не входит** — он детерминированно
пересобирается из чекпоинтов. Батч-подтверждение терминов (Фаза 1) применяется только на границе джоб.

### 3.3 Деньги: атомарность settle+checkpoint

Дыра «settle прошёл — kill -9 — чекпоинт не записан» = двойная оплата вызова. → **Settle и запись сырого
ответа — одна SQLite-транзакция в одном файле БД** (ledger и чекпоинты живут в проектной БД). Тогда
неустранимая потеря — только in-flight вызов (провайдер списал, ответ не дошёл) — это и есть честные
«≤1 вызов» приёмки. На старте процесса — recovery-проход: снять зависшие reserve джоб без чекпоинта.

### 3.4 Ключ TM (Фаза 1, фиксируется сейчас — влияет на схему store)

`tm_key = (lang_pair, brief_hash, sha256(нормализованный СЫРОЙ src-чанка))` — нормализация только
whitespace/Unicode NFC, **до** pre-replace терминов (иначе подтверждение термина инвалидирует TM через
чёрный ход, что прямо запрещено Р6). `prompt_version`, `model`, `glossary_version` — метаданные записи,
НЕ ключ. Открытые вопросы политики инвалидации — [НУЖНО РЕШЕНИЕ] п.2.

> **ПОПРАВКА (оркестратор ответил, PROGRESS/Р6 — источник истины).** Этот абзац устарел: оркестратор
> зафиксировал ключ TM `(chunk_src_hash, lang_pair, model_id, prompt_version, brief_hash,
> context_snapshot_hash)` — то есть **`model_id` и `prompt_version` ВХОДЯТ в ключ**, а не в метаданные
> (тюнинг промпта/смена модели легитимно инвалидируют TM; перечанкование — by design). Имплементеру Фазы 1:
> строить ключ TM по PROGRESS/Р6, не по букве этого абзаца. В Фазе 0 это неважно (cross-book TM не
> реализован — есть только checkpoint/resume request-hash, который `model`+`prompt_version` уже включает
> через snapshot).

### 3.5 Нейтральный интерфейс LLM (расширения к типам vojo)

- `Message.CacheBoundary bool` — конец стабильного блока префикса. Anthropic-адаптер вешает `cache_control`
  на соответствующий блок (максимум 4 брейкпоинта); OpenAI-совместимые адаптеры игнорируют (автокэш по
  префиксу). Единственная точка, где Р5 материализуется в коде, — раскладку задаёт сборщик контекста, не адаптеры.
- `Usage{Prompt, Cached, CacheCreation, Completion, Reasoning}` — семантика reasoning per-adapter
  (xAI аддитивно, OpenAI-спека/ollama subset → 0, Anthropic thinking внутри output → 0) документируется в
  каждом адаптере, как в доноре.
- `ReasoningEffort`: значение `"off"` — явное выключение thinking; маппится адаптером в провайдер-специфичный
  флаг. Для нестандартных ручек (GLM `thinking.type=disabled`, Qwen `enable_thinking=false`) —
  `extra_body` per-модель в models.yaml, мёрджится в JSON запроса OpenAI-совместимым адаптером
  (эмпирика полигона: thinking GLM-4.6 — таймауты ×3, Gemini молча жрёт max_tokens на thinking).
- Стриминг в Фазе 0 НЕ реализуется, но интерфейс проектируется под него (второй метод в будущем не ломает
  адаптеры); длинные генерации закрываются длинными attempt-таймаутами из профиля. См. [НУЖНО РЕШЕНИЕ] п.4.
- DeepSeek-адаптер парсит **оба** варианта cache-полей usage (`prompt_tokens_details.cached_tokens` и
  `prompt_cache_hit_tokens`/`prompt_cache_miss_tokens`) — иначе эксперимент Фазы 0 по кэшу покажет ложный ноль.
- Ответ транспорта читается через `LimitReader` (у донора кап есть только в gemini-native).
- Failover-декоратор портируется, но состав пары primary/fallback задаётся снаружи per-роль
  (позже per-канал: в канале B fallback = только пермиссивный провайдер или fail loudly — иначе пустой ответ
  локальной abliterated молча уйдёт в облачную ногу, дыра в изоляции Р4).

### 3.6 SQLite-рецепт (modernc.org/sqlite)

- На файл БД два пула: write (`MaxOpenConns=1`, транзакции `BEGIN IMMEDIATE`) и read (N соединений).
- Прагмы на соединение: `journal_mode(WAL)`, `busy_timeout(5000)`, `foreign_keys(1)`, `synchronous(NORMAL)`
  (для kill -9-приёмки достаточно; отключение питания — вне модели угроз MVP).
- Замена advisory-локов: сериализация check-and-reserve через write-пул + `BEGIN IMMEDIATE`.
- Регистр/леммы кириллицы нормализуются на стороне Go до записи (NOCASE в SQLite — ASCII-only);
  сравнения в SQL — только по нормализованным колонкам.
- FTS5 в горячий путь v1 не тащить (unicode61 не сегментирует CJK; селективной инъекции хватает точных
  ключей/алиасов по обычным индексам).
- Файлы БД — только на ext4 (WSL: не /mnt/c — WAL-локи ломаются на drvfs); задокументировать для установок.

### 3.7 Coverage-гейт v1 (Фаза 1, спека фиксируется сейчас)

Портировать классификацию исходов из `eval/refusal_bench.py` (hard_refusal / soft_refusal /
excision_suspect / ok) 1:1, не изобретать заново. Метрика — **символы без пробелов** (не токены!);
пороги per-пара из эксперимента 02: нижние zh<2.2 / ja<1.4 / en<0.70 (вырезание), верхние >4.2 / >2.6 / >1.4
(аномалия), sent_cov<0.75; минимальная длина применения (калибровка на 500–2500 симв.). Сегментация — класс
`[。！？.!?…]` со схлопыванием последовательностей терминаторов и поглощением закрывающих кавычек/скобок.
Отдельные ветки по `finish_reason`: `content_filter` = refusal; `length` = обрезание бюджетом → ретрай с
бОльшим max_tokens, НЕ эскалация (реальный кейс Gemini из эксп. 02). Blacklist refusal-паттернов — en/ru/zh/ja.

### 3.8 Конфиги (граница Р2 — закрытие 6 недоопределённостей)

Три файла: `book.yaml` (translation brief + ссылки) → `configs/pipeline-c1.yaml` (ядро) →
`configs/models.yaml` (модели/цены/таймауты, с датой проверки). В конфиг ядра входят: состав/порядок стадий,
роль→модель, версии промптов, пороги гейтов, **режим инъекции глоссария (selective | full_prefix)** (Р5
требует обе схемы), токен-бюджеты сборки контекста (инъекция, STM, overlap), лимит регенераций до эскалации,
**именованные эскалационные цепочки** (список моделей; channel-aware фильтр применяет раннер), fan-out N для
C2, cache TTL per-стадия. В models.yaml: цены (+cache write/read), профиль таймаутов/ретраев per-модель,
`extra_body`. Зашито в раннер: циклы по главам/чанкам, ветвление по гейтам, механика эскалации/ретраев,
семантика «эскалация → re-gate → флаг» (диаграмма pipeline.puml перегейтовку не рисует — реализую re-gate
с ограничением попыток, иначе эскалированный вывод коммитится непроверенным).

### 3.9 Онлайн-факты (проверены 2026-07-04, источники — официальные доки)

- DeepSeek: `deepseek-chat`/`deepseek-reasoner` отключаются 24.07.2026 15:59 UTC (сейчас алиасы
  `deepseek-v4-flash`); v4-flash: $0.14 in / $0.28 out / $0.0028 cache-hit, контекст 1M. Пиковых надбавок
  для V4 официально НЕТ (только пресс-слух) — в конфиг не закладывать, заложить переключаемость тарифа.
- Anthropic: cache write ×1.25 (5m) / ×2 (1h), read ×0.1; поля `cache_creation_input_tokens` /
  `cache_read_input_tokens`; максимум 4 брейкпоинта; **минимальный кэшируемый префикс зависит от модели**
  (Sonnet 5 — 1024 ток., Opus 4.8 — 1024, Haiku 4.5 — 4096) — короче лимита кэш молча не создаётся;
  thinking биллится как output (полный объём, даже при summarized-отображении). Sonnet 5 промо $2/$10 до
  31.08.2026 (кэш write $2.50/5m, hit $0.20; batch $1/$5); Opus 4.8 $5/$25.
- GLM (Z.AI, международный): флагманы уже GLM-5.1/5.2 ($1.4/$4.4), GLM-5 $1/$3.2 (cached $0.2).
- Kimi: актуальны kimi-k2.6 ($0.95 in / $4.00 out / $0.16 cache-hit) и k2.7-code; API-домен теперь
  platform.kimi.ai (301 с platform.moonshot.ai).
- xAI: grok-4.3 $1.25/$2.50 (1M контекст); Grok 4.1 Fast retired (см. §2 п.4).
- Gemini: 3.1 Pro (Preview) $2/$12 до 200k промпта, $4/$18 свыше; batch −50%. Эмпирика полигона: judge-роли
  нужен нативный API (safety_settings через OpenAI-слой не передаются) — нативный Gemini-адаптер планирую
  Фазой 2 (в vojo есть донорское v1beta-лицо), в Фазе 0 закладываю только место в раскладке адаптеров.

Ловушки телеметрии кэша: TTL 5m может истекать между вызовами одной стадии (латентности чанков 63–278 с +
гейты) — TTL параметр стадии; телеметрия обязана показывать `cache_read=0 при повторных вызовах` (тихая
инвалидация). DeepSeek-кэш best-effort — экономику считать только по фактическому usage.

## 4. Мелкие поправки к research/10 (для истории)

- request_log донора — 46 колонок после v8, не 43; тест-функций 137, не «125+».
- «Table-driven тесты» — преувеличение: основной стиль донора — сценарные функции + httptest wire-контракты +
  конкурентные тесты под `-race`; копирую именно этот стиль.
- Из config.go переносимы хелперы (~150–200 строк: getenv/getSecret `*_FILE`/fail-fast «problems списком»/
  Summary с redaction), а не ~400; YAML-слой конфигов — целиком новый код (yaml.v3 в доноре только для Matrix).
- telemetry/trace: `safego` живёт в bot.go — выносится в obs; reqInfo (sender/verbose) заменяется на ось
  book/chapter/chunk/role; прайсеру нужен явный default-price якорь (у донора — `Prices[XAIModel]`).

## 5. [НУЖНО РЕШЕНИЕ] — продублировано в PROGRESS (## Бэкенд)

1. **Премиум-бюджет Р5 «≤2× input-объёма»** после токен-калибровки неоднозначен (какой токенизатор, входит ли
   output; эксп. 01: полный проход уже ~2.8×B o200k-input). Реализую как конфигурируемый $-потолок книги
   (без зашитого «2×»); формулу должен уточнить владелец/оркестратор.
2. **Политика инвалидации TM** сверх brief_hash: (а) входит ли prompt_version в ключ (да = тюнинг промпта
   обнуляет TM библиотеки; нет = TM молча переживает смену промптов); (б) модель в ключе или метаданных
   (эскалация перезаписывает запись или дополняет?); (в) смена параметров чанкера = полная инвалидация —
   трактовать как «смену brief» с показом стоимости? Реализую (а) нет, (б) метаданные+дополняет, (в) да —
   но это меняет экономику пере-переводов, нужно подтверждение.
3. **Канал B без дешёвого Grok**: Grok 4.1 Fast retired; grok-4.3 — $1.25/$2.50 (~9× дороже по input).
   Основную модель канала B на Фазу 2 надо перевыбрать (кандидаты: open-weights через OpenRouter, DeepSeek,
   локальная abliterated). Фазу 0–1 не блокирует (интерим-правило 18+ действует).
4. **Стриминг**: план относит к Фазе 3 «нужен только IDE-фронту», но эмпирика полигона требует его как
   keep-alive транспорта для генераций 63–278 с (иначе фейловер ложно роняет здоровую локалку). В Фазе 0
   закрываю длинными таймаутами из профиля; предлагаю оркестратору перенести провайдерский стриминг
   (не SSE-фронт!) в Фазу 1–2.
5. Оркестратору (не блокирует): цифры Р5 устарели ~2× против эксп. 01 (Sonnet-проход $46, не $92); задача
   «проверка cache-hit агрегаторов» назначена и Фазе 0 бэкенда, и бэклогу полигона — предлагаю отдать
   полигону (у него харнесс сравнения usage), бэкенд потребит результат в конфиге; TMX в диаграммах не
   помечен «(Фаза 3)»; морфодетектор/CometKiwi/NSFW-классификатор в диаграммах без фазовых пометок;
   режим онгоинга отсутствует в диаграммах (и не определено, перегоняется ли Analyst при дозагрузке глав).

## 6. Селфревью Фазы 0 (агентное, 2026-07-04)

Финал Фазы 0: `/code-review` (8 finder-линз + верификация) + воркфлоу-критики (4 линзы задания: архитектура/
соответствие v2, корректность и деньги, идиоматичность Go, устойчивость к сбоям) с адверсариальной проверкой
каждой находки. Подтверждённые исправлены **двумя волнами** (коммиты `dc6ea8e` → `a32ec2e` → фиксы v2):

**Волна 1 (коммит a32ec2e):** общий retry-движок (устранена копипаста Anthropic↔OpenAI, кап Retry-After
`maxRetryAfterWait` 5м, overflow-safe backoff); `BilledDecodeError` — 2xx с нечитаемым телом консервативно
селтлит оценку, не возвращает резерв; пустой оплаченный ответ — громкая остановка, не отравляет редактуру;
snapshot-pinning гейт с `--resnapshot`; flock-владение проектным файлом; цены локальных тегов $0; однопроходный
рендер; `temperature` без omitempty (Anthropic вообще не шлёт sampling — 400); `attempt` в ключе и схеме.

**Волна 2 (эта):** snapshot-покрытие расширено (Defaults ratio/min, Title→BriefHash, модельный ExtraBody —
правки конфига больше не инвалидируют чекпоинты в обход гейта); `PriceForResponse` — неизвестный слаг ответа
книжится по ЗАПРОШЕННОЙ модели, не по дешёвому якорю (устранён недоучёт премиума); usage-кламп в `CostUSD`
(отрицательные числа не уменьшают committed); zero-usage платный 2xx → settle оценки + WARN; жизненный цикл
статуса джобы (не остаётся `running` на отказе потолка/ошибке клиента); `CheckRunnable` — fail-fast на
нереализованную механику (C2/C3, fan-out>1, роль judge — иначе `pipeline-c2.yaml` прогнал бы стадии линейно и
сжёг деньги Opus на мусор); `CheckKeys` — preflight API-ключей used-моделей (пустой ключ ловится до reserve, не
401-м после); request-hash — length-prefix полей + счётчик сообщений (устранена NUL-коллизия); нормализация
источника BOM/CRLF/NFC (детерминизм хеша между ОС/редакторами); LimitReader-усечение терминально (не ретраит
оплаченный >16 MiB); recoverReservations → stderr.

**Остаточный техдолг (осознанные ограничения Фазы 0, не блокеры):**
- **Дневной потолок — пер-книжный** (ledger в файле проекта одной книги): `day_usd` сбрасывается в UTC-полночь
  и ограничивает burn-rate одной книги, но не «на оператора за день по всем проектам». Общий потолок оператора
  потребует shared spend-файла — отложено (раскладка «файл БД на книгу» из Р3/Р6); пометить в UI при мультикниге.
- **Retryable-но-оплаченные вызовы не идемпотентны** (уточнено внешним ревью F3): 2xx с НЕусечённым
  нечитаемым телом и чистый таймаут/обрыв до тела после серверного биллинга — retryable, поэтому цепочка
  ретраев может оплатить **до `MaxAttempts−1` лишних вызовов** (не «≤1», как я записал раньше — граница
  занижена была). Потолок к ним слеп; краха нет, триггер редкий (повторный оплаченный сбой). Честный фикс —
  idempotency-ключ на стороне провайдера (поддерживают не все), а не «сделать всё терминальным» (уронит
  прогон на транзиентном сбое прокси). Обсудить с владельцем/оркестратором; отложено до Фазы 1.
- **finish=length с непустым обрезанным черновиком** (F4): течёт в edit с `OK=true` без `Degraded`-флага
  (раннер ловит только пустоту). Асимметрия наблюдаемости — свернуть в редизайн обработки негодного вывода
  Фазы 1 (та же семья, что «пустой ответ»): `Degraded:"truncated_length"` + WARN, или skip+flag по `attempt`.
- **`prices_checked` (120д) гейтит read-only `report` и $0-resume** (F6): они платных вызовов не делают, но
  падают на устаревших ценах. Мелкая связанность — проверять свежесть цен только там, где будет платный вызов.
- **cache_ttl ↔ cache_write_per_m**: цена записи в кэш в models.yaml задаётся под ОДИН TTL (у нас 5m). Если
  оператор поставит `cache_ttl: 1h`, реальная запись стоит ×2 (не ×1.25) — недоучёт. Боевой конфиг 5m совпадает;
  при переходе на 1h требуется отдельная цена. Валидацию/вторую цену — Фаза 1 (когда включим 1h осознанно).
- **Гонка `Runner.clients` map**: ленивая инициализация клиентов не синхронизирована — безопасна в Фазе 0
  (последовательный проход стадий), но параллельные чанки/fan-out Фазы 1–2 потребуют мьютекса/предзагрузки.

**Внешнее (независимое) ревью Фазы 0 (2026-07-04).** Отдельная сессия провела построчное чтение всего
`backend/` + исполняемую проверку приёмки + адверсариальный воркфлоу (22 агента). **Вердикт: Фаза 0 принята,
блокеров ноль**; двухволновое селфревью подтверждено закрывшим реальные вещи; детерминизм-машинерия признана
пропорциональной (не переинженерена). Диспозиция находок (F-нумерация ревью):
- **F1** (CheckKeys префлайтит эскалационные ключи) — **уже исправлено** до ревью (сужен до достижимых моделей:
  стадии всегда, эскалация — только при включённом гейте; бэкенд заводится на 2 ключах).
- **F2** (local-оверрайды temp/max_tokens вне snapshot), **F5** (fail-open на `gates.enabled=true`),
  **F7** (устаревший §3.4 vs подтверждённый ключ TM), **F8** (`*.db.lock` в .gitignore) — **исправлено этой
  волной** (F2/F5 — тесты добавлены; F7 — поправка-сноска выше в §3.4).
- **F3/F4/F6** — в техдолг выше (обоснованно отложены).
- **Дизайн-вопросы к владельцу/оркестратору (D1–D3):** **D1** — редактор C1 видит исходник (`{{text}}` в
  editor.md); для Фазы 1+ сделать редактора монолингвальным (только `{{draft}}`), верность → coverage-гейт +
  билингвальный судья (совпадает с ролевой моделью Р2); в Фазе 0 билингвальный редактор — защитимый интерим до
  появления гейта/судьи, но `{{text}}` убрать, когда они приедут. **D2** — «пустой/негодный ответ = падение
  всего прогона» неверно для Фазы 1 (много чанков); нужен per-chunk skip+flag (совпадает с Р4: ложное
  срабатывание не должно убивать многочасовую джобу) — закроет механизм `attempt`. **D3** — изоляция канала B
  в failover не принуждается кодом (пара primary/fallback задаётся вызывающим); при подключении failover
  (Фаза 2) нужен структурный запрет Anthropic в канале B, иначе пустой ответ local-abliterated молча уйдёт в
  Anthropic. Все три — Фаза 1–2, не блокеры Ф0.
