# Отчёт бэкенд-сессии: ручка эффорта · экран целевого языка · холодный прогон (строки 104 · 46 · 16) > ✅ **ПРИНЯТ приёмкой оркестратора №11 (02.08, D39.91). Код заленден коммитом `553f1a3` (13 файлов backend/, pathspec-форма).** Приёмка исполнением: батарея пере-прогнана — все пять фрозен-чисел совпали (голден PASS без пере-захвата · парити EXACT `n=13618 {方源:0 蛊:1 蛊师:2 古月:22} recall 0.9655` · labels-фриз · K6 1/6/0/251); деньги пере-выведены вторым путём — леджер $0.045095 ≡ независимый пересчёт usage×цены ≡ Σ `request_log`, дельта $0.000000, `decode_error`/`estimated` 0; адверсариал — рукописных `config.Stage{}`/`Request{}` вне швов НОЛЬ (grep), **пять НЕЗАВИСИМЫХ посадок приёмки** (алиас-литерал · `[]cfg.Stage{{…}}` · hoisted `Request` · `var`-форма · `new()`) все CAUGHT; парная выборка N=5 сверена с сырьём `classifier-6of6-{low,high}.json` побайтно. **Обе девиации РАТИФИЦИРОВАНЫ** (repair-ключ свой — деньги+семантика подтверждены по пяти шиппинг-конфигам; `ThinksOnWire` — область правки ровно «ключ отсутствует», провод не менялся). **Поправки приёмки:** (1) §2.1 «$0.031639 (flash)» — на деле flash+pro черновой волны (flash **$0.021289** + pro $0.010350; итоги и леджер верны, врёт метка; так же скопировано в D39.90 п.2); (2) клейм «`8e4495006b978ed4` в обе стороны» невоспроизводим (старая форма удалена, хеш не запинен) — принят ПО ЭКВИВАЛЕНТНОСТИ (длина-префикс `Reasoning` + опущенное поле = `""` + `TestBankProbeAndAttemptAddressOneCheckpoint` + голден); (3) стейл-доккоммент `capability.go:78` («effort "" leaves the provider default» у `ReasoningExtraBodyDisable`) противоречит проводу и новому предикату — носитель: строка 114. Отложки Д4–Д9 получили носителей: 114 (Д4) · 115 (Д6) · 116 (Д8) · реестр 108 (Д5, Д9) · строка 107 (Д7). **Промт:** `docs/BACKEND_EFFORT_HANDLE_SESSION_PROMPT.md` (оркестратор №10, D39.87). **Дата:** 02.08.2026. **Не коммичено** — лендит оркестратор. **ИСХОД ЗАХОДА:** работа A **ЗАКРЫТА**; §3.4 (платная проба банка) **ЗЕЛЁНАЯ**; работа B — **дизайн подан, кода нет**, стоит на решении владельца; работа C **НЕ НАЧАТА** и закрыта владельцем как неуспешная на этом этапе, возвращена оркестратору. Деньги: **$0.045095** из $0.10 (проба §3.4), прогон C — $0. --- ## §0. Эхо-шапка 1. Скоуп: A — ручка эффорта ролям без неё; B — экран целевого языка; C — добивка холодного прогона. 2. A: один шов деривации синтетической стадии на все 4 места + обе расщеплённые хеш-пары. 3. §3.4: своя проба `reprobe/bank-low/`, потолок $0.10, вердикт «низкий эффорт не ломает банк» — только предъявив, что ни один из двух молчащих путей не сработал. 4. B: вердикт-двигающее ⇒ дизайн и пинг ДО кода. 5. C: только после заморозки A и B. 6. Деньги: A $0 · §3.4 ≤$0.10 · B $0 · C ≤$5 (не потрачено). 7. Вне скоупа: сид · судья · DC7 · `models.yaml` capabilities/price · thinking-off · `optNoThink` · чужие файлы · коммиты. --- ## §1. Работа A — ручка эффорта (строка 104). ЗАКРЫТА ### 1.1. Форма шва **Корень дефекта — не «забыли ключ», а форма кода.** Четыре рукописных `config.Stage{...}` по движку, каждый молча терял поля родителя. Починка четырёх мест копипастой тиражирует дефект на пятое. Построено **два шва**: | Шов | Файл | Что закрывает | |---|---|---| | `config.InternalCall{...}.Stage()` | `internal/config/internal_call.go` (новый) | единственная деривация стадии для внутренних вызовов движка | | `Runner.attemptRequest(...)` | `internal/pipeline/stagerun.go:406` | единственная сборка кортежа идентичности запроса | Рукописных `config.Stage{}` в движке **не осталось ни одного**. Все четыре места (`terminologist.go:515`, `:568`, `repair.go:384`, `live_reprobe_test.go:98`) едут через шов. Все четыре сборки `RequestHash(Request{...})` схлопнулись в одну. ### 1.2. Деньги правки: перекупки НЕТ. Проверено хешами Ключевой вопрос любого шва, трогающего идентичность запроса: не осиротели ли уже оплаченные чекпойнты. - **`gates.terminology` НЕ фолдится в снапшот** (в `snapshot.go` ссылки на `Terminology` нет — это осознанное решение, см. доккоммент `config.TerminologyGate`). ⇒ добавление ключа `reasoning` **не двигает `snapshotID` и не пере-биллит ни одной волны**. - **Кортеж идентичности для всех предсуществующих путей воспроизведён побайтно.** `RequestHash` длина-префиксует `req.Reasoning`, а опущенное поле в композитном литерале И ЕСТЬ `""` ⇒ старая и новая формы совпадают **по построению, а не по везению**. Бэнк-пробник с незаданным ключом даёт `8e4495006b978ed4` в обе стороны; эскалационный хоп — равенство; draft/edit — тот же список полей, перенесённый дословно. - **Голден НЕ пере-захватывался** и не должен был: `classifierVersion` не бампнут (работа B не начата), `CheapGateVersion`/`SanitizerVersion` не тронуты. `TestGoldenDeterminism` PASS на исходных фикстурах — это и есть доказательство, что вердикты и провод не сдвинулись. - **Единственное, что двигало бы хеш, — наследование эффорта у repair, и оно снято** (§1.6). ### 1.3. Ответы на четыре приёмочных вопроса §0 промта **(1) Новый механизм или использование существующего?** Существующего. `Stage.Reasoning` было с самого начала, проброс на провод был, капабилити-слой различает провайдеров данными. Добавлен ОДИН конфиг-ключ `gates.terminology.reasoning` и ОДИН `gates.repair.reasoning`; новых механизмов нет. **(2) Появится ПЯТАЯ синтетическая стадия — унаследует сама или забудут?** Унаследует, и это **механика, а не обещание** — три гарда: | Гард | Что физически не даёт сделать | Проверено исполнением | |---|---|---| | `TestSyntheticStageSeamIsSingle` | построить `config.Stage` мимо шва | 6 посадок, все CAUGHT | | `TestRequestIdentitySeamIsSingle` | пересобрать кортеж идентичности руками | посадка CAUGHT | | `TestInternalCallDecidesEveryStageField` | добавить в `Stage` поле, не решив, что с ним делают внутренние вызовы | добавил поле → FAIL с именем поля | ⚠ **Первая версия гардов была дырявой, и это найдено ревью, а не мной.** Байтовый скан пробивался пятью способами; после переписи на AST — ещё двумя (алиас импорта `cfg "…/internal/config"` и `[]config.Stage{{…}}` с опущенным типом элемента). Обе дыры я **перепроверил на живом дереве** — гард говорил `ok`. Причина: сравнивал имя пакета вместо пути импорта. Итоговая версия резолвит путь, разворачивает `[]T`/`map[K]T`/`*T`, исключения полными путями (а не именами файлов, которые расползались на одноимённые файлы по всему репо), и несёт **пол на число разобранных файлов** — обход, не распарсивший ничего, выглядел как чистое дерево. **(3) Заработает ли пара, которой нет в репо, без правки Go?** Да. Ключ пар-слепой, валидируется общим `ValidReasoningEffort`, Go не ветвится ни по паре, ни по книге. Вторая книга той же пары — без новых флагов (ключ ран-скоупный, в `book.yaml` и пар-пак не заходит). **(4) Не появилось ли второго способа сказать то же самое?** Проверено и вычищено в трёх местах: enum эффорта — один валидатор на стадии и оба гейта; `bankRolePlan.model` снят (был вторым источником правды, читался только логом); идентичность запроса — одна сборка. **Остаточный долг признаю:** в репо теперь два гарда РАЗНОЙ формы — мой на AST и давний `TestProviderEgressSeamIsSingle` на байтовом регэкспе, с другим корнем обхода и своим словарём исключений. Свести в один обход — правильно, но это правка чужого давно живущего теста; вынесено предложением (§6, Д7). ### 1.4. Гранулярность ручки — решено ЗАМЕРОМ, не заранее Приор был «одна ручка на бэнк-блок; вторая появится по замеру». Замер парный, N=5 на уровень, через боевой путь: | Уровень | 6/6 достигнуто | completion-токены | цена 5 вызовов | |---|---|---|---| | `low` | 4 из 5 | 91–1278 | $0.001016 | | `high` | 5 из 5 | 920–3104 | $0.002335 | 4/5 против 5/5 на n=5 **неразличимо**, а `high` стоит 2.3×. ⚠ **Самопоправка:** первый вызов дал 3/6, я на этом основании построил вторую ручку `classify_reasoning` — и **снял её**, когда выборка не подтвердила разницу. Единственный промах — вызов на 91 токене, то есть модель просто не подумала; уровень тут ни при чём. **Итог: одна ручка на обе бэнк-роли.** Классификатор сохраняет собственную МОДЕЛЬ (`classify_model`), но не собственный эффорт. ⚠ **Честная граница самих чисел 6/6** (унаследована от D39.86, повторяю, чтобы не читалась сильнее, чем есть): 元石 и 灵泉 стоят ДОСЛОВНО в `prompts/zh-ru/classifier.md`, то есть два из шести — припоминание, а не суждение. Дискриминирующее подмножество — 元海/蛊室/池塘/酒肆, и падение на `low` было ровно по нему (1/4 против 4/4). Читать как «4 решено + 2 припомнено». **Точки шва в коде после правок:** `attemptRequest` — `stagerun.go:406`; `bankRoleSettings` — `terminologist.go:534`; `bankStage` — `terminologist.go:553`; `repairStage` — `repair.go:363`. (Номера `terminologist.go:515,568`, `repair.go:384`, `live_reprobe_test.go:98` в тексте — ИСХОДНЫЕ места литералов, как их называет D39.87.) ### 1.5. `Temperature` — зафиксирована нулём осознанно Ответ бэнк-роли и repair — разбираемая движком СТРУКТУРА (таблица терминов, тип, спан-замена), где разброс сэмплинга — чистый риск; температура стадии это СТИЛЕВОЙ выбор про прозу и здесь бессмысленна. Ноль — то же значение, при котором куплены все существующие бэнк-чекпойнты, так что решение ещё и не двигает хеш. Причина первая, совпадение второе. ### 1.6. Repair — ключ СВОЙ, наследование СНЯТО Первая версия наследовала эффорт от чинимой стадии (так предлагал промт и D39.87). **Ревью это опровергло, я перепроверил и согласился:** - **Деньги:** `reasoning: "off"` стоит на финальной стадии у **всех** шиппинг-пайплайнов (`pipeline-c1.yaml:77`, `c2:51`, `arm-glm:43`, `arm-deepseek-pro:43`, стенд coldrun-b:44). Наследование двигало хеш ⇒ перекупка всех уже оплаченных repair-вызовов. - **Семантика, и это хуже:** `reasoning` — не уровень, а значение, чьё ЗНАЧЕНИЕ зависит от control модели. Финальная стадия `deepseek-v4-pro` с `reasoning: "off"` — задокументированный no-op; `gates.repair.model: glm-5` — и тот же «off» становится живым `thinking:{type:disabled}`, то есть эхо-зоной. Родительская стадия и repair — РАЗНЫЕ модели. Решение: `gates.repair.reasoning` — собственный ключ. Не задан ⇒ `""` ⇒ побайтно прежнее поведение. ### 1.7. Мульти-провайдерность — и найденная при этом дыра Промт требовал закрыть режим `extra_body_disable` (glm), где ПУСТОЙ эффорт означает не «дефолт провайдера», а thinking ВЫКЛЮЧЕН. Расширил `sourceEchoExposure` на внутренние вызовы движка — и **ревью показало, что этого мало**: предикат `ThinksOnWire` для этого control возвращал `reasoning != "off"`, то есть на ПУСТОМ значении отвечал «думает», расходясь с проводом, который в этом же случае мержит disable. Проверено исполнением: ``` applyToBody effort="" -> wire={"max_tokens":…, "thinking":{"type":"disabled"}} ThinksOnWire(glm-подобная, "") = true <-- врал ``` Починено, запинено `TestThinksOnWireMirrorsTheWireForEachControl` (8 кейсов на три control). Проверил, что на старом коде тест падает. ⚠ Это была **предсуществующая** дыра (она так же молчала про СТАДИИ на glm без ключа `reasoning`), но мой пак первым на этот предикат оперся, поэтому чиню здесь. ### 1.8. Наш enum против вендорского — решение и явный отказ `ValidReasoningEffort` принимает `"" | off | low | medium | high` — НАШ провайдер-слепой набор. У DeepSeek документированы `low/high/max` (+`xhigh`): `medium` там неопределённое поведение, `max`/`xhigh` нашим конфигом недостижимы. **Валидацию против капабилити резолвнутой модели НЕ строил** — это потребовало бы пер-модельной таблицы enum'ов в `models.yaml`, которой нет, то есть НОВЫЙ механизм (нарушение §0 п.1). Дыра предсуществующая и одинаковая для `stages[].reasoning`. Записано находкой (§6, Д5), не закрыто молча. ### 1.9. Доккомменты эхо-гейта — обязанность D39.86 п.2 / D39.87 Приведены в соответствие `config/models.go` (`thinkingControlExtraKeys`) и `llm/capability.go` (`ReasoningNone`). Ключевая формулировка: **запрещён не ПРЕДМЕТ, а сырой канал** — уровень эффорта через `stages[].reasoning`/`gates.*.reasoning` легален и идёт через `Capability.applyToBody`, который знает, что «off» значит для этого control; `model.extra_body` мержится дословно и потому может ВЫКЛЮЧИТЬ thinking, что и есть мина. Гейт **не ослаблен**. --- ## §2. §3.4 — платная проба банка на `low`. ЗЕЛЁНАЯ **Хост:** `~/books/gu-zhenren/coldrun-b/reprobe/bank-low/` — свой `book_id`, своя БД, свой леджер. Эталон `coldrun-a` только на ЧТЕНИЕ (срез глав 1–10), не тронут. ### 2.1. Числа ``` терминолог 4/5 батчей finish=stop · consolidated=72 · unanswered=18 bad_lines=0 · off_language=0 · canon_conflicts=0 · $0.005750 классификатор 5/5 stop · reclassified=14 · $0.004286 черновая волна 20/20 чанков ok · 25 свежих вызовов · 2 эскалации вылечили эхо $0.031639 (flash) + $0.010350 (pro) ``` Против вендор-дефолта `high` (замер D39.86): **1 батч из 5 и 1 терм из 81**. ### 2.2. Оба молчащих пути ИСКЛЮЧЕНЫ предъявлением Промт требовал: вердикт «работает/не работает» имеет право быть вынесен только предъявив, что ни ролевой суб-бюджет, ни книжный потолок не сработали. | | потрачено | бюджет | % | |---|---|---|---| | терминолог | $0.005750 | $0.05 | 11.5% | | классификатор | $0.004286 | $0.02 | 21.4% | | книга | $0.045095 | $0.10 | 45.1% | И в логе **ноль** строк `budget would be exceeded by the next batch` / `denied by a USD ceiling`. ### 2.3. Провод вскрыт 35 тел (25 translator + 5 terminologist + 5 classifier), ключи ровно `[max_tokens, messages, model, reasoning_effort, stream, temperature]`, `reasoning_effort:"low"` **во всех 35**, ключей `thinking` — **0**, `reasoning_content` непустой везде. **Гардрейл «thinking у DeepSeek не выключать» цел и доказан проводом, а не рассуждением.** Упавший батч вскрыт сырьём (требование промта — прошлая сессия этого не сделала): `content:""`, `finish=length`, `completion_tokens=7999`, `reasoning_tokens=7999`. То есть **тот же тяжёлый хвост, что при `high`, просто на 1/5 вместо 4/5** — `low` стену не убирает, а разрежает. Батч #2 не самый большой по входу (5909 симв. против 5890/5806), то есть стохастика вызова, согласуется с квирками п.6. ### 2.4. Деньги двумя путями ``` леджер (Σ чекпойнтов) $0.045095 независимый пересчёт usage×вендор-цены $0.045095 дельта $0.000000 разбивка: основной заход $0.041675 риг 6/6 (11 вызовов: 1 + 5 на low + 5 на high) $0.003420 ``` Расхождения строки 78 (2xx с битым декодом) в этом заходе НЕ было — 0 строк `decode_error`. ### 2.5. Правки стенда, объявленные построчно `~/books/gu-zhenren/coldrun-b/reprobe/bank-low/pipeline.yaml` — тело ран-конфига coldrun-b с ТРЕМЯ отличиями: 1. `stages[draft].reasoning: "low"` вместо no-op `"off"` — иначе волна упирается в стену и до банка проба не доезжает. 2. `gates.terminology.reasoning: "low"` — НОВАЯ ручка, предмет замера. 3. `escalation.budget_usd: 0.015` вместо `0.10` — **понижен сознательно**: эскалации идут ДО банка, при $0.10 они могли съесть книжный потолок раньше первого вызова терминолога, и проба ответила бы «банк не работает» на деньгах. ⚠ Это делает наблюдение эскалаций НЕПОЛНЫМ; предмет пробы не они. Боевой `coldrun-b/pipeline.yaml` **не тронут** (там по-прежнему `reasoning: "off"` на черновике — правка под работу C, которая не стартовала). --- ## §3. Работа B — экран целевого языка (строка 46). ДИЗАЙН ПОДАН, КОДА НЕТ Промт: «вердикт-двигающее ⇒ сначала дизайн и пинг, потом код». Пинг подан владельцу, решения нет ⇒ кода нет. Это легитимный исход по букве промта. ### 3.1. Живая улика, добытая в этом заходе **Глава 8, чанк 1 пробы bank-low:** полный связный черновик **на английском**, `finish=stop`, `disposition=ok`, и его хеш стоит в `final_hash` чанка. Хеш `fb51e135a395`, 5990 симв., 4690 букв, латиница 100%, кириллица 0.000. Второй такой же в `optB` (`4080ee8287af`, 4811 букв). Плюс полный **японский** черновик в `exp15/anchors/oracle_30.db` (`94905a7e443f`). ### 3.2. ⚠ ПОПРАВКА К МОИМ ЖЕ ЧИСЛАМ Я подал владельцу «здоровый русский 0.9510–1.000, n=1306». **Это измерено на СЫРОМ тексте чекпойнта.** Движок классифицирует ПОСЛЕ среза банкноты (`stagerun.go:632` режет, `:636` классифицирует; срез безусловен для роли translator, `banknote.go:320`), то есть гейт видит другой текст. Пере-мерил его же функциями (`splitBanknote` + `checks.StripThink`): | | я подал | на тексте, который видит гейт | |---|---|---| | здоровый минимум | 0.9510 | **0.9767** | | n | 1306 | **1055** (дедуп по хешу; 1306 двоил бэкап-копии БД) | | off-target выходов | 26 строк | **20 событий** | Решения это не меняет (оба числа далеко от любого порога), но число я называл — поправляю. ### 3.2а. Поправка к БАЗЕ отказа Публиковалось «~1.5% off-target». Знаменатель неверен: он пулит черновики с РЕДАКТОРСКИМИ выходами, а редактор этот отказ произвести не может (он не переводит с исходника). На черновой вызов база **≈3.0%**. Оба числа — одна книга, один жанр, по сути одна модель. ### 3.3. ⚠ ГЛАВНАЯ ПОПРАВКА: боевой гейт уже ловит 18 из 20 Ни ресерч, ни я не измеряли ДО этого, что делает **уже развёрнутый** гейт. Измерил: ``` sourceScriptShare > 0.15 (disposition.go:224,275) поймано: 18 из 20 off-target событий ложных: 0 из 1055 здоровых запас: худший здоровый 0.0117 против порога 0.15 — 13× промах: ровно 2, оба английские, 5990 и 6078 симв. ``` То есть движок **не открыт настежь**. Остаточная дыра — **2 чанка из ~1100 = 0.18%**, оба английские. Японский ловится (доля ханьцзы 0.332, японский делит письменность с китайским). **И второй рубеж существует, но стоит не там.** Прогнал боевой санитайзер по тем самым байтам английского черновика: `total=1, cosmeticOnly=false, "Latin insertion"` — он бы его поймал. Не ловит потому, что `chunkrun.go:52` требует `isFinal`, а `isFinal := stageIdx == len(Stages)-1` (`stagerun.go:145`) ⇒ **в шиппинговой двухстадийной топологии черновик переводчика НИКОГДА не финальный**. Тот же `isFinal`-гейт глушит на черновике латинный лint, флаггер голоса и глоссарный пост-чек. Плюс он опционален (`gates.sanitizer.enabled`) и требует данных цели. ⇒ Формулировка «ни один предикат не ловит» верна **только для черновой стадии**. На финале для русской цели с включённым санитайзером ущерб = потеря юнита + оплаченный впустую редактор, а не тихая отгрузка. Для любой другой цели слой 7 инертен по отсутствию данных и рубежа нет вовсе. ### 3.4. Дизайн, поданный на решение **Носитель:** `lang.LangScripts(Book.TargetLang)` — тот же дата-план, на котором стоит эхо-детектор, тот же `data/lang-script.txt`, где `ru→cyrillic` уже объявлено. **Новый файл в `go:embed` НЕ добавляется** — это критично: `EmbeddedVersion` фолдится в снапшот БЕЗУСЛОВНО, то есть новый эмбед-файл перекупает все книги всех пар. ⚠ `gates.terminology.target_script` как носитель **отвергнут**, но по причине сильнее промтовой: он ДУБЛИРУЕТ то, что данные уже знают, и потому это уже существующий «второй способ сказать то же самое». Промт снимал его за «нет в шиппинг-конфигах» — на деле ключ ОБЯЗАТЕЛЕН при включённом гейте (`pipeline.go:1075`). **Порог 0.50** — не калиброван, а сознательно консервативен: пустой интервал между 0.0000 (все 20 событий) и 0.9767 (худший здоровый) шириной 0.9767, порог поставлен в его середину. Калибровать по Нейману–Пирсону на этом корпусе нельзя (§5.2). **Развилка, которую промт требовал НАЗВАТЬ, а не предрешать** — подана владельцу трёхчастной: | | Что | Цена | |---|---|---| | (а) | предикат внутри `classify()` + бамп `classifierVersion` | снапшот двигается у всех книг; на свежем стенде $0, голден пере-захват один раз | | (б) | отдельный опциональный гейт вне `classify()` | снапшот не двигает, но это второй способ сказать «ответ непригоден», и он выключен по умолчанию | | (в) | сразу языковой детектор | см. §5 | Мой выбор — **(а)**. ### 3.5. Вторая половина дыры — решение `detectLatinInsertion` (`checks/sanitizer.go:343`) внутри санитайзера НЕ обусловлен `TargetScriptNonLatin()`, хотя доккоммент `disposition.go:302-305` объявляет именно эту защиту (реальный гард стоит только на репэйр-пути). Следствие: первая же ЛАТИНОПИСЬМЕННАЯ цель дропнет 100% чанков чистой прозы. **Решение: чинить здесь же, тем же диффом** — это одна работа с первой половиной. Для русской цели вердикты не меняются ни на байт (`TargetScriptNonLatin()` для ru истинно), то есть бампа `SanitizerVersion` не требуется. **Не сделано** — работа B не начата. ### 3.6. Что бы сломалось, если бы я сделал B наивно Ревью и аудит нашли три мины, в которые я бы въехал: 1. **Новый флаг провалился бы в дыру.** `escalatable()` — закрытый белый список, `retryable()` — только `{length, empty}`. Новая причина не попадает ни туда, ни туда ⇒ пойманный чанк **молча терялся бы вместо перекупки**. Правильное решение — переиспользовать `FlagCJKArtifact` (он уже escalatable и не retryable), а не заводить новую константу. 2. **Классификатор ложно флагается 16 из 16 уже сегодня** (`SourceEchoExpected: role == roleTerminologist` не покрывает `roleClassifier`). Мой экран флагнул бы его вторым способом. Экран обязан читать не роль-строку, а факт формы ответа, проставленный тем же швом работы A. 3. **Экран слеп к ПОДМНОЖЕСТВАМ письменности:** у японского набор {ханьцзы, хирагана, катакана} ⇒ чисто китайский выход для →ja книги наберёт 1.000 и пройдёт. То же для →ko. --- ## §4. Работа C — НЕ НАЧАТА Причина по букве промта: «идёт только после того, как работы A и B ЗАКРЫТЫ И ЗАМОРОЖЕНЫ». Работа B застряла на дизайн-пинге — промт прямо называет это легитимным исходом захода. Дополнительно владелец закрыл прогон как неуспешный на данном этапе и вернул его оркестратору. **Стенд `~/books/gu-zhenren/coldrun-b/` стоит нетронутым:** свежая БД, сид не подключён, `pipeline.yaml` побайтно равен эталону coldrun-a кроме шапки, черновик по-прежнему на `reasoning: "off"` (правка под C не внесена — вносить её без прогона значило бы оставить стенд в промежуточном состоянии). Строка 16 (полная цена холодного старта включая редактуру) **НЕ закрыта**. Строка 13б (оси голоса) **без данных** — редакторская волна снова не гонялась. --- ## §5. Ресерч-задел для следующих сессий Два холодных исследования, каждое с независимыми скептиками (author≠reviewer). Полные тела — в транскриптах воркфлоу; ниже то, что должно пережить сессию. ### 5.1. Off-target детекция: закрыта ли задача **ЧАСТИЧНО, и нерешённая часть меньше, чем казалось.** **Устоялось (не пере-открывать):** - У отказа есть имя и метрика: *off-target translation* (Zhang et al., ACL 2020, arXiv 2004.11867), *language confusion* LPR/WPR (Marchisio et al., EMNLP 2024, arXiv 2406.20052). Рецепт у всех один и он у нас уже реализован: детектор на выходе → сравнение с запрошенной целью → mismatch = отказ. **Более умного serving-time метода не упущено.** - Английский — доминирующий аттрактор. Подтверждено дважды независимо (Cohere; Guerreiro et al., TACL 2023: >90% off-target галлюцинаций при переводе С английского). Совпадает с нашими 2/20 английских. - Четыре режима отказа — практика поля, не новизна: Guerreiro использует РАЗНЫЙ детектор на режим (ALTI+ для detached, top-n-gram для oscillatory, LID только для off-target). Гнать всё через один детектор — ошибка проектирования, и мнение из обсуждения тут право. - Abstain + пол по длине — опубликованная практика (Cohere `compute_metrics.py`: строка оценивается только при `len(tokens) >= 5`). - Reference-free QE эту задачу не делает, и поле пишет об этом прямо. **Открыто у всех:** - Близкие кириллические соседи. OpenLID-v3 на реальном пользовательском тексте: боснийский 37.21% precision, сербский-латиница 30.85% recall; 19.9% ошибок — «полная неоднозначность». Трёх девяток тут не достигает никто. - FPR детектора на настоящей ЦЕЛЕВОЙ прозе длиной в чанк не опубликован нигде. - Дрейф в середине генерации: нет устоявшейся статистики, гранулярности и правила агрегации. - Ничего в этой литературе не про длинную художественную прозу. Caswell et al. 2020: >90% held-out F1 соответствовал ~5% точности по человеческой оценке «в дикой природе». ### 5.2. Почему обещанной гарантии FPR ≤ 0.1% здесь не купить Правило трёх: ноль отказов на n независимых испытаний покупает `1 − 0.05^(1/n)`. Для ≤0.1% нужно n ≥ 2995. У нас **114 различных позиций (глава, чанк)** — одна книга, один жанр, одна модель — это покупает **FPR ≤ 2.594%**, в 26 раз слабее обещанного. Даже если считать все 1055 выходов независимыми — ≤0.283%. Плюс процедура двоит данные: «калибруй так, чтобы ложное отклонение ≤0.1%» ставит порог в эмпирическую 0.001-квантиль ТОЙ ЖЕ выборки, по которой потом заявляется граница. При n≈1000 порог определяется вторым наименьшим наблюдением. И третье: Нейман–Пирсон ограничивает ошибку I рода и **ничего не говорит про мощность**. На сиблингах распределения нулевой и альтернативной гипотез по доле целевого письма СОВПАДАЮТ (болгарский даёт ровно 1.0000). Односторонний срез при α=0.001 даёт мощность ≈0.1%: гарантия есть, ловли нет. ⇒ **Ставить руками консервативную константу в широком пустом интервале и версионировать её как контракт, а не как тюнинг.** ### 5.3. Что НЕ строить (и почему) — самая ценная часть 1. **Никакую статистическую LID-библиотеку в путь вердикта.** Проверено запуском, не по README: `lingua-go` — НЕдетерминистична (6/23 образцов на 20 000 повторов; `GOMAXPROCS=1` не лечит — причина в обходе map при суммировании float64), +126 МиБ эмбеда безусловно, 10–12 мс/чанк против бюджета 168 мкс, и вендор документирует вердикт-двигающие апгрейды. `langid-go` — на 644 реальных сегментах русской Википедии даёт P(ru)<0.5 на **22.2%** сегментов длиной 20–40 рун; плюс FMA-зависимость от GOARCH. `whatlanggo` — 4–6 из 9 настоящих русских образцов принимает за болгарский/македонский. `getlang` — argmax флипается. `gocld3`/fastText — CGo, вердикт становится функцией тулчейна, то есть уезжает ВНЕ снапшот-хеша. 2. **Посегментный worst-of (L4 из мнения).** Умножает FPR на число сегментов ПО ПОСТРОЕНИЮ, и — решающее — **не имеет ни одного положительного примера**: самая длинная не-кириллическая цепочка в здоровом выходе 23 символа, между 0.0000 и 0.9767 не лежит ничего. На замеренных точках стоило бы 74–290 дропнутых чанков на книгу ради нуля дополнительных поимок. Цена (269 мкс) никогда не была препятствием — препятствие в калибровке и пустом классе положительных. 3. **Замыкание алфавита (L2).** Против болгарского доказуемо ноль (его алфавит — собственное подмножество русского), в остальных случаях избыточно с L1. 4. **Функционально-словный экран.** Здоровый русский p01 = 0.2433 на выход и 0.0455 на абзац — зазор крошечный, а русский закрытый класс во многом и болгарский. На этой машине нет ни одного сиблинг-текста, чтобы померить. 5. **Эхо через расстояние редактирования / n-граммное сходство.** `sourceScriptShare > 0.15` уже даёт 18/20 при 0 FP и 13× запасе. 6. **Любой новый файл в `go:embed`-манифесте** ради этой фичи. `EmbeddedVersion` фолдится БЕЗУСЛОВНО ⇒ перекупка всех книг всех пар. 7. **Hunspell в любом виде** и **GlotScript-эмбед** (stdlib `unicode` уже несёт ISO-15924-совместимые таблицы, движок к ним уже ходит). ### 5.4. Пошаговый план для следующей сессии Принцип порядка: единственный необратимый шаг — бамп `classifierVersion`, он последний. Шаги 1–3 независимы. 1. **Корпус-фикстура в репо** (~20 off-target + ~40 здоровых, с движковой предобработкой). Снапшот НЕ двигает. СТОП, если не воспроизводит 18/20 при 0 FP. 2. **Записать реальный запас боевого гейта регресс-тестом** (худший здоровый source-share 0.0117 против 0.15). Это база, которую обязано побить любое предложение, и её никто не записывал. СТОП, если запас меньше 3×. 3. **Поправить два дока-дефекта** — опубликованный диапазон 0.9510 и база 1.5% (правильно: 0.9767 на 1055 движково-чистых, и база 3.02% на ЧЕРНОВОЙ вызов, а не на все выходы, потому что редактор этот отказ произвести не может). Зона оркестратора. 4. **`targetScriptShare` чистой функцией, НЕ подключённой.** Снапшот НЕ двигает — в этом и смысл, дорогое отложено. СТОП, если хоть один здоровый ниже 0.90. 5. **Прогнать предлагаемый вердикт офлайн по всем сохранённым чекпойнтам** и сдиффить диспозиции. Ожидание: ровно **2 строки** меняются. **СТОП+эскалация, если изменилась хоть одна здоровая.** 6. **Подключить и бампнуть `classifierVersion`** — ДЕНЕЖНЫЙ шаг, только с подписью владельца. Переиспользовать `FlagCJKArtifact` (уже escalatable). Порог abstain — **200 букв** (ниже него экран воздерживается; самое короткое off-target событие — 346 букв, ниже 200 падают лишь 5 из 1055 здоровых, все со счётом 1.0000). 7. **Починить `cultivation` в сиде, а не порогом.** 86 из 1055 здоровых выходов содержат голое английское `cultivation` внутри беглого русского; ВСЕ 32 абзаца, набравшие ниже 0.90, содержат его, и НИ ОДИН не содержит ханьцзы. Это дефект глоссария, не калибровочная константа. Правка в пар-данных двигает `LangpackVersion`, который фолдится с `omitempty` ⇒ перекупка ограничена книгами ЭТОЙ пары. Это самый переносимый вывод всей цепочки: **чинить на том плане, где перекупка уже.** 8. **Записать сиблинг-угрозу непостроенной строкой с триггером.** Ни одного события не наблюдалось. Если появится — дискриминатор это плотность РУССКО-ЭКСКЛЮЗИВНЫХ букв (ы/э/ё) НИЖЕ ожидания, а не замыкание алфавита. ### 5.5. Ресерч про механизм гардов (побочный, но применён) Форма «архитектурное правило как обычный тест» — **стандартная**: `go/build/deps_test.go` в самой stdlib держит так граф зависимостей («*It is a statement of policy. DO NOT CHANGE THIS DATA TO FIX BUILDS*»), `go/types/errorcalls_test.go` — буквально `ast.Inspect` по своим исходникам. Аналог в Java — ArchUnit. Альтернативы проверены запуском и **не проходят наше ограничение «падать на обычной батарее»**: `go test -vet=<свой анализатор>` → *«-vet argument must be a supported analyzer»*; `GOFLAGS=-vettool` при `go test` игнорируется; штатный `composites` вообще не входит в набор `go test`. `golangci-lint` — 38.7 МБ бинарь и первый в проекте `.golangci.yml`. `x/tools` в тесте тянет `x/net` 0.26.0 → 0.54.0, а `x/net/http2` — наш транспорт к провайдерам. `exhaustruct` — инструмент ровно под исходный класс бага, но на нашем коде даёт 16 находок, из которых 15 в тестах и одна — сам конструктор. --- ## §6. Дефекты: найдено / починено / отложено ### Починено с тестами (в этом заходе) | № | Дефект | Тест | |---|---|---| | П1 | Синтетические стадии теряли `Reasoning`/`Temperature` — 4 места | `TestSyntheticStageSeamIsSingle` + `TestInternalCallDecidesEveryStageField` | | П2 | `bankCheckpointExists`/`repairCheckpointExists` строили кортеж БЕЗ `Temperature`/`Reasoning`, расходясь с `runAttempt` в ОБЕ стороны (ложно-false режет резюм бюджетом; ложно-true обходит ролевой суб-бюджет) | `TestRequestIdentitySeamIsSingle`, `TestBankProbeAndAttemptAddressOneCheckpoint` (3 уровня эффорта, боевой путь `Reserve`→`SettleWithCheckpoint`) | | П3 | `ThinksOnWire` на `extra_body_disable` врал про пустой эффорт ⇒ бэнк-роль на glm уезжала в thinking-off МОЛЧА | `TestThinksOnWireMirrorsTheWireForEachControl` | | П4 | `sourceEchoExposure` не видел внутренние вызовы движка | расширен; детерминизм порядка — `sort.Strings` | | П5 | Гарды пробивались 7 способами (5 байтовых + алиас импорта + опущенный тип элемента) | переписаны на AST, все 7 посадок CAUGHT | | П6 | Поведенческие тесты были вакуумны (сравнение вызова с самим собой; смена двух полей вместо одного) | переписаны на боевой денежный путь | | П7 | `bankRoleSettings` молча отдавал бы настройки терминолога неизвестной роли | явная паника с именем роли | | П8 | `bankRolePlan.model` — второй источник правды, читался только логом | снят | | П9 | Риг с N=1 по умолчанию при комментарии, осуждающем n=1 | дефолт 5 | ### Найдено, НЕ починено — с носителем | № | Дефект | Носитель | |---|---|---| | Д1 | Экран целевого языка (работа B) | решение владельца по развилке §3.4 | | Д2 | `detectLatinInsertion` не обусловлен `TargetScriptNonLatin()` ⇒ первая латинописьменная цель дропнет 100% чанков | тем же диффом, что Д1 | | Д3 | `SourceEchoExpected` не покрывает `roleClassifier` ⇒ 16/16 вызовов классификатора логируются `ok=0 degraded=cjk_artifact`. Данные НЕ теряются (`run.texts[i] = att.text` безусловен, `reclassified=14` доказывает), но отравлен ровно тот сигнал, который говорит «провайдер плохеет» | тем же диффом, что Д1 | | Д4 | `gates.terminology.target_script` не сверяется с `book.target_lang`: `target_lang: ru` + `target_script: Latin` грузится чисто и **инвертирует** банковский экран | бэкенд, отдельный пак; вне снапшота ⇒ дёшево | | Д5 | Наш enum эффорта провайдер-слеп: `medium` на DeepSeek — неопределённое поведение, `max`/`xhigh` недостижимы | решение оркестратора: нужна ли пер-модельная таблица | | Д6 | `//go:embed` в `internal/lang/embedded.go:22` — ЯВНЫЙ список файлов, не `data/*`. Новая цель ⇒ правка Go. `CompileCheckers` паникует при неполном файле цели. На ревью-вопрос «заработает ли пара, которой нет в репо» ответ сегодня **НЕТ** — и это не про язык, а про строку эмбеда | архитектура, оркестратор | | Д7 | Два гарда шва разной формы (AST против байтового регэкспа), с разным корнем обхода и разными словарями исключений | бэкенд; свести в один обход | | Д8 | Ратифицированный порог классификатора «6/6» определён для ОДНОГО вызова, не для выборки. На `low` — 4/5. Риг сейчас красный на рекомендованном уровне, и это заявлено в тексте падения, а не смягчено | владелец/оркестратор | | Д9 | Аддитивные провайдеры отказываются для бэнк- и repair-гейтов ИМЕННО потому, что блок не несёт `reasoning_max_tokens`. Новая ручка эту дыру НЕ закрывает | зафиксировано комментарием; вскрывать под отдельное решение | | Д10 | **Шиппинговые конфиги репо всё ещё несут `reasoning: "off"` на ЧЕРНОВОЙ стадии** (`configs/pipeline-c1.yaml:49`, `c2:26,39`, `arm-mistral:30`, `arm-deepseek-pro:34`, `arm-glm:34`) — тот самый no-op, который отправил прогон в стену 0/9 при вендор-дефолте `high`. Санкция (б) по D39.87 касается и их, но правка **двигает снапшот всем, кто этими конфигами пользуется** ⇒ молча не менял | владелец/оркестратор: применять ли (б) к шиппинг-конфигам и когда платить перекупку | | Д11 | Область правки `ThinksOnWire` — уточнение, чтобы её не переоценили: **ЯВНЫЙ `"off"` на glm предупреждался и до правки, и после** (в `pipeline-arm-glm.yaml:43` он стоит осознанно и с измеренной причиной — «таймауты ×3»). Правка меняет ровно один случай: **ключ ОТСУТСТВУЕТ**. Там предикат отвечал «думает», а провод мержил disable. Дыра предсуществующая и одинаковая для стадий и для гейтов | закрыто; строка оставлена, чтобы приёмка не искала регресса в армах | --- ## §6а. Батарея числами (после ВСЕХ правок, включая пост-ревью) ``` go build ./... exit 0 go vet ./... exit 0, пусто go vet -tags live ./internal/pipeline/ exit 0, пусто gofmt -l . пусто go test -race -count=1 ./... все 14 пакетов ok (pipeline 82.0s, store 15.9s) TestGoldenDeterminism PASS (голден НЕ пере-захватывался — нечему двигаться) майнер-парити EXACT n=13618 catastrophe{方源:0 蛊:1 蛊师:2 古月:22} recall@proposed=0.9655 (56/58 GT) labels-фриз k4b 10/1/36/99 · k4_inverse 3/0/1/71 · k2 34/1/6/51 · k4a 0/0/0/348 K6 (membank) tp=1 fp=6 fn=0 tn=251 ``` Все пять фрозен-чисел совпали с ратифицированными. Батарея гонялась **трижды**: после работы A, после снятия второй ручки, после пост-ревью фиксов. ## §7. Заявление = команда | Число | Команда | |---|---| | батарея (build/vet/vet-live/race/gofmt) | `go build ./... && go vet ./... && go vet -tags live ./internal/pipeline/ && gofmt -l . && go test -race -count=1 ./...` | | голден PASS | `go test ./internal/pipeline/ -run TestGoldenDeterminism -count=1` | | парити EXACT `n=13618 catastrophe{方源:0 蛊:1 蛊师:2 古月:22} recall 0.9655` | `TM_MINER_PARITY=1 go test ./internal/miner/ -run Parity -count=1 -v` | | labels-фриз k4b 10/1/36/99 · k4_inverse 3/0/1/71 · k2 34/1/6/51 · k4a 0/0/0/348 | `TM_CHECKER_LABELS=1 go test ./internal/checks/ -run Label -count=1 -v` | | K6 1/6/0/251 | `TM_CHECKER_LABELS=1 go test ./internal/membank/ -run K6 -count=1 -v` | | проба §3.4, все числа стадий | `LOG_LEVEL=debug LOG_LLM_BODIES=1 ./bin/tmctl translate --config ~/books/gu-zhenren/coldrun-b/reprobe/bank-low/book.yaml --verify-bank` | | терминолог 72/18, off_language=0 | `grep "terminology finished" ~/books/gu-zhenren/coldrun-b/reprobe/bank-low/run-01.stderr.log` | | леджер $0.045095 | `sqlite3 "file:…/guzhenren-bank-low.db?mode=ro" "SELECT SUM(cost_usd) FROM checkpoints;"` | | независимый пересчёт $0.045095 | `SELECT SUM((prompt_tokens-cached_tokens)*0.14/1e6 + cached_tokens*0.0028/1e6 + completion_tokens*0.28/1e6) FROM request_log WHERE tm_hit=0 AND model_actual LIKE 'deepseek-v4-flash%'` + pro вручную по `models.yaml:161` | | 35 тел с `reasoning_effort:"low"`, 0 ключей `thinking` | `grep -o 'reasoning_effort[^,}]*' run-01.stderr.log \| sort \| uniq -c; grep -c '"thinking"' run-01.stderr.log` | | классификатор 4/5 и 5/5 парно | `TM_LIVE=1 TM_CLASSIFY6_N=5 TM_CLASSIFY6_CONFIG=…/bank-low/book.yaml go test -tags live -run TestLiveClassifierHarmSet -count=1 -v ./internal/pipeline/` (сырьё: `classifier-6of6-{low,high}.json`) | | боевой гейт 18/20, FP 0/1055, запас 13× | временный тест в пакете pipeline через `splitBanknote`+`StripThink`+`sourceScriptShare`; удалён после снятия | | санитайзер поймал бы английский черновик | временный тест: `CompileCheckers(nil, lang.TargetChecksFor("ru")).SanitizeOutput(…)` → `total=1 cosmeticOnly=false "Latin insertion"`; удалён | | валидация гейта эффорта громкая | `./bin/tmctl status --config <копия с reasoning: "sorta">` → `gates.terminology.reasoning must be off\|low\|medium\|high` | | 7 обходов гарда пойманы | посадки в `waverun.go` + `go test ./internal/pipeline/ -run SeamIsSingle`, дерево восстановлено (`git diff --stat` пуст) | --- ## §8. Критик полноты против себя - **Качество прозы не судил никто, ни в одной пробе.** `low` мерился детерминированными осями. Черновик без глубокого размышления может быть заметно хуже как ТЕКСТ, проходя все $0-гейты. Это работа судьи Ф2, он не гонялся. - **`low` вернул волну к жизни, но стену не убрал** — 1 батч из 5 по-прежнему упирается. При другом размере книги/батча доля может быть иной; n=5 батчей. - **Редакторская волна не гонялась НИ РАЗУ** — ни до смены весов, ни после. Класс отказа, убивший черновик и терминолога, на редакторе не проверен, а рычага там нет (маппинг pro `low→high`). Строка 13б без данных. - **Проба bank-low понизила `escalation.budget_usd`** — наблюдение эскалаций неполное. - **Работа B не начата**, а её улика (английский черновик) воспроизвелась в этом же заходе. Дыра открыта. - **Числа off-target — одна книга, один жанр, по сути одна модель.** Сиблинг-угроза не наблюдалась ни разу; всё, что про неё сказано, — арифметика алфавитов и авторские образцы, не корпус. - **Первые версии моих гардов и тестов были дефектны**, и нашёл это ревью, а не я. Это второй за заход случай, когда я подал вывод, который не выдержал проверки (первый — 3/6 классификатора на n=1). Оба поправлены до сдачи; оба записаны здесь, а не сглажены. - **Не проверял**, что происходит с английским черновиком в РЕДАКТУРЕ — вытащит ли редактор его обратно в русский или отредактирует как английский. Проба остановилась на банк-паузе. ## §9. Адверсариальный проход по своим правкам Запущен независимым агентом (author≠reviewer, мандат CLAUDE.md 12.07) по всему диффу с явным заданием «считать сломанным, пока не доказано обратное». Вернул **10 находок**: 1 blocker, 3 major, 5 minor, 1 nit. Все разобраны; топ-2 я **перепроверил кодом сам** до принятия (`ThinksOnWire` — исполнением `applyToBody`; наследование repair — по всем шиппинг-конфигам). Проверенные им и НЕ нашедшие дефектов области: идентичность кортежа для всех предсуществующих путей (хеши совпали побайтно, бэнк-пробник с незаданным ключом → `8e4495006b978ed4` в обе стороны), полнота шва по всем 17 полям `Stage`, конкурентность (`-race` чист), общность (ветвлений по языку/паре/книге нет). --- ## §10. Тронутые файлы (только своё) ``` M backend/internal/config/models.go ThinksOnWire + доккоммент эхо-гейта M backend/internal/config/pipeline.go gates.terminology.reasoning, gates.repair.reasoning, валидация M backend/internal/config/capability_test.go +TestThinksOnWireMirrorsTheWireForEachControl M backend/internal/llm/capability.go доккоммент ReasoningNone M backend/internal/pipeline/stagerun.go attemptRequest M backend/internal/pipeline/escalation.go хоп через attemptRequest M backend/internal/pipeline/terminologist.go bankStage/bankRoleSettings, пробник, смета, plan без model M backend/internal/pipeline/repair.go repairStage, пробник, доккоммент M backend/internal/pipeline/runner.go sourceEchoExposure на внутренние вызовы M backend/internal/pipeline/live_reprobe_test.go N-выборка, шов, порог ?? backend/internal/config/internal_call.go ШОВ ?? backend/internal/config/internal_call_test.go полнота полей ?? backend/internal/pipeline/synthetic_stage_seam_test.go гарды швов M docs/PROGRESS.md секция «Бэкенд», допись СВЕРХУ ?? docs/archive/reports/EFFORT_HANDLE_2026-08-02.md этот отчёт ``` Вне git (артефакты): `~/books/gu-zhenren/coldrun-b/reprobe/bank-low/` — проект, БД, `run-01.stderr.log`, `wire-terminologist-batches.json`, `classifier-6of6-{low,high}.json`. Чужого не тронуто: `START_PROMT.MD`, `frontend/`, `platform/`, `.gitignore`, `docs/` кроме этого отчёта и §Бэкенд в `PROGRESS.md`. Коммитов нет. **СТОП — приёмка оркестратора.**