Freeze the step-4 pre-registration and prove from bought cells that Gemini drops the FIRST system message, so the engine loses the stage prompt rather than the glossary

This commit is contained in:
heaven 2026-08-29 21:32:01 +03:00
parent 28c2d0fd50
commit d7a202edc6
4 changed files with 369 additions and 8 deletions

View file

@ -154,8 +154,8 @@
## Контент-фильтры / safety
- ⛔⛔ **Gemini, ДВА СИСТЕМНЫХ СООБЩЕНИЯ = ПРОМТ ТЕРЯЕТСЯ МОЛЧА (полигон 22.08, живая проба `eval/dovodka/proba_gemini.py`).** **МЕХАНИЗМ НАЗВАН** (вендор-дока `ai.google.dev/api/generate-content`): в нативном API `systemInstruction` — это ОДИН объект `Content`, поэтому слой ФИЗИЧЕСКИ не может пронести больше одного системного, и уход на нативный API проблемы НЕ решает — там пришлось бы склеивать так же. При двух и более лишние выбрасываются без ошибки. ⚠ КАКОЕ именно выживает — НЕ УСТАНОВЛЕНО: форум Google (discuss.ai.google.dev/t/86097, 30.05.2025) сообщает «выживает последнее», а наш замер даёт 535 токенов ≈ размер одного user-сообщения, то есть не выжило НИ ОДНО. Расхождение открыто. Документированного вендором ограничения в разделе «Current limitations» НЕТ. Замер на одном и том же системном тексте в 7970 знаков: ОДНИМ сообщением → `prompt_tokens` 2994 и маркерная инструкция ИСПОЛНЕНА; ДВУМЯ → **535 токенов (это размер одного user-сообщения) и инструкция НЕ исполнена**. Ответ приходит с `finish=stop` и выглядит правильным переводом — отказа нет, есть тихая потеря инструкции, невидимая всем гейтам годности. ⚠ Так были куплены 16 клеток арма `RE` (промт-роль на gemini): они переведены ВООБЩЕ БЕЗ ИНСТРУКЦИИ, и вердикт о переносимости из них был бы ложным. Лечение: склеивать системные в одно ДЛЯ ЭТОГО ВЕНДОРА (`rol.ONE_SYSTEM`/`fit_msgs`) — общая склейка сменила бы форму запроса у всех моделей и рассогласовала бы уже купленные клетки. ⚠ Проверено, что у `deepseek-v4-pro`, `gpt-5.6-luna`, `glm-5` и `grok-4.3` два системных доезжают целиком.
- ⛔⛔ **ТОТ ЖЕ ДЕФЕКТ ЖИВЁТ В ДВИЖКЕ, А НЕ ТОЛЬКО В ПОЛИГОНЕ (аудит 22.08).** `backend/internal/pipeline/render.go:256-258` штатно строит ВТОРОЕ системное сообщение — инъекцию банка памяти (глоссарий/STM), `backend/internal/llm/httpllm.go:517-522` копирует сообщения дословно, склейки под Gemini нет нигде, а `gemini-3.1-pro-preview` стоит в ратифицированной цепочке эскалации во ВСЕХ арм-конфигах (`pipeline-c1.yaml:129`, `pipeline-arm-glm.yaml:61`, `pipeline-arm-deepseek-pro.yaml:62`, `pipeline-arm-mistral.yaml:61`). ⇒ **любой эскалационный хоп на Gemini при непустой инъекции ТИХО теряет глоссарий.** Оговорка D22.3 «экспозиция Ф1 нулевая» держится только на том, что до Gemini в дефолте не доходят. ⚠ Класс в проекте уже был решён рядом: `backend/internal/llm/provider_anthropic.go:169-179` сворачивает ведущий system-префикс в одно поле и падает громко на system посреди диалога — просто не обобщён на Gemini. **Зона бэкенда, пинг оркестратору отправлен.**
- ⛔⛔ **Gemini, ДВА СИСТЕМНЫХ СООБЩЕНИЯ = ПРОМТ ТЕРЯЕТСЯ МОЛЧА (полигон 22.08, живая проба `eval/dovodka/proba_gemini.py`).** **МЕХАНИЗМ НАЗВАН** (вендор-дока `ai.google.dev/api/generate-content`): в нативном API `systemInstruction` — это ОДИН объект `Content`, поэтому слой ФИЗИЧЕСКИ не может пронести больше одного системного, и уход на нативный API проблемы НЕ решает — там пришлось бы склеивать так же. При двух и более лишние выбрасываются без ошибки. **УСТАНОВЛЕНО 29.08: ВЫЖИВАЕТ ПОСЛЕДНЕЕ СИСТЕМНОЕ, ВЫБРАСЫВАЕТСЯ ПЕРВОЕ** (полигон, Д35.3 отчёта 23; $0, детерминированно, на 16 уже купленных клетках). Доказательство арифметическое: у арма `RE` первое системное (ролевой промт) **побайтно одинаково на всех 16 главах**, второе (глоссарий) — **разное** (255404 знака, термины из текста главы); разность `RE.prompt_tokens RQ.prompt_tokens` равна **2436 РОВНО на каждой из 16 глав, sd = 0.00** ⇒ выброшено сообщение ПОСТОЯННОГО размера, а таково только первое; будь выброшены оба, разность росла бы вместе с глоссарием. Побочно: `prompt_tokens` карантинных клеток коррелируют с длиной глоссария +0.52 и с длиной исходника +0.04 — глоссарий внутри них физически есть. ~~КАКОЕ именно выживает — НЕ УСТАНОВЛЕНО~~ — форум Google (discuss.ai.google.dev/t/86097, 30.05.2025) был ПРАВ, наша прошлая интерпретация «не выжило НИ ОДНО» неверна: она сравнила 535 токенов с «размером одного user-сообщения» на глаз, а при шкале 0.324 ток/знак русского глоссарий в 300 знаков весит ~100 токенов и в такой оценке теряется. Документированного вендором ограничения в разделе «Current limitations» НЕТ. Замер на одном и том же системном тексте в 7970 знаков: ОДНИМ сообщением → `prompt_tokens` 2994 и маркерная инструкция ИСПОЛНЕНА; ДВУМЯ → **535 токенов (это размер одного user-сообщения) и инструкция НЕ исполнена**. Ответ приходит с `finish=stop` и выглядит правильным переводом — отказа нет, есть тихая потеря инструкции, невидимая всем гейтам годности. ⚠ Так были куплены 16 клеток арма `RE` (промт-роль на gemini): они переведены ВООБЩЕ БЕЗ ИНСТРУКЦИИ, и вердикт о переносимости из них был бы ложным. Лечение: склеивать системные в одно ДЛЯ ЭТОГО ВЕНДОРА (`rol.ONE_SYSTEM`/`fit_msgs`) — общая склейка сменила бы форму запроса у всех моделей и рассогласовала бы уже купленные клетки. ⚠ Проверено, что у `deepseek-v4-pro`, `gpt-5.6-luna`, `glm-5` и `grok-4.3` два системных доезжают целиком.
- ⛔⛔ **ТОТ ЖЕ ДЕФЕКТ ЖИВЁТ В ДВИЖКЕ, А НЕ ТОЛЬКО В ПОЛИГОНЕ (аудит 22.08).** `backend/internal/pipeline/render.go:256-258` штатно строит ВТОРОЕ системное сообщение — инъекцию банка памяти (глоссарий/STM), `backend/internal/llm/httpllm.go:517-522` копирует сообщения дословно, склейки под Gemini нет нигде, а `gemini-3.1-pro-preview` стоит в ратифицированной цепочке эскалации во ВСЕХ арм-конфигах (`pipeline-c1.yaml:129`, `pipeline-arm-glm.yaml:61`, `pipeline-arm-deepseek-pro.yaml:62`, `pipeline-arm-mistral.yaml:61`). ⇒ ~~**любой эскалационный хоп на Gemini при непустой инъекции ТИХО теряет глоссарий.**~~ ⛔⛔ **ФОРМУЛИРОВКА ИСПРАВЛЕНА 29.08 И СТАЛА ХУЖЕ, А НЕ МЯГЧЕ.** По установленному выше правилу выживает ПОСЛЕДНЕЕ системное, а `render.go:247-258` кладёт первым **сам промт стадии**, вторым — инъекцию банка. ⇒ **на эскалационном хопе в Gemini при непустой инъекции движок теряет НЕ глоссарий, а ВЕСЬ ПРОМТ СТАДИИ, и оставляет глоссарий.** Модель получает список канонных форм и текст — без единого слова о том, что с ними делать. Ровно так выглядят 16 клеток `RE`: перевод есть, `finish=stop`, русский, правильной длины, инструкции за ним нет. **Тихая потеря промта не отличима от нормального ответа ничем, кроме счётчика входных токенов.** Лечение прежнее (склейка системных под этого вендора), приоритет — выше. Оговорка D22.3 «экспозиция Ф1 нулевая» держится только на том, что до Gemini в дефолте не доходят. ⚠ Класс в проекте уже был решён рядом: `backend/internal/llm/provider_anthropic.go:169-179` сворачивает ведущий system-префикс в одно поле и падает громко на system посреди диалога — просто не обобщён на Gemini. **Зона бэкенда, пинг оркестратору отправлен.**
- **Gemini**: `safety_settings` через OpenAI-совместимый слой **не передаются** (HTTP 400 «Unknown name safety_settings»). Дефолт фильтров у 2.5/3 — OFF (gap-2). Для явного контроля фильтров (роль судьи 18+) нужен **нативный** Gemini API, не OpenAI-слой. ⚠ **D22.6: на ГРАФИЧНОЙ эротике Gemini 3.x fail-closed `PROHIBITED_CONTENT` (8× wire-verified, exp11) — фильтр НЕконфигурируем, нативный API не спасает; судья эротики — только Grok. На violence/SFW Gemini-судья жив.** `finish_reason` приходит СОСТАВНОЙ строкой `'content_filter: PROHIBITED_CONTENT'` (точные матчеры мертвы — D22.8а). Биллинг Gemini: thinking ТОЛЬКО в `total_tokens``reasoning: additive_total`, иначе недоучёт 146×/вызов (D22.3). Нативное имя поля — `usageMetadata.thoughtsTokenCount`; слой AI-Studio `completion_tokens_details.reasoning_tokens` не отдаёт ВОВСЕ, тогда как Vertex AI то же поле отдаёт (форум 15.01.2026) — при переезде на Vertex учёт менять. ⚠⚠ **`reasoning_tokens=0` у Gemini — НОРМА, а не измерение: вывод «размышление выключено» из этого поля НЕДЕЙСТВИТЕЛЕН.** Полигон 22.08 на этом и поскользнулся, объявив «вендор-дефолт flash-lite = размышление ВЫКЛ (замерено)»; считать надо `total prompt completion`. ⚠ **В роли редактора Gemini течёт сервис-преамбулой в выход («Вот отредактированный…», 6/6 чанков — exp12) → за output-санитайзером D30.3.****Дополнение эксп-21 (08.08):** fail-closed ответ OpenAI-слоя приходит вовсе БЕЗ объекта `message` (не с пустым `content`) — парсер обязан переживать его отсутствие; на вебновелльном violence-материале срабатывания массовые (10 клеток судейского прогона с `content_filter: PROHIBITED_CONTENT`) ⇒ оговорка «на violence/SFW Gemini-судья жив» целиком НЕ держится — Gemini непригоден как ЕДИНСТВЕННЫЙ второй контур на этом материале (эксп-21 §1/§8).
- **Qwen Model Studio**: внешний фильтр `data_inspection_failed` на вход/выход, неотключаем (риск молчаливых вырезаний) — не тестировано (нет ключа).
- Детект отказов/вырезаний — см. `eval/refusal_bench.py` (паттерны soft-отказа + coverage-гейт по len_ratio/sent_cov, пороги в эксп. 02).

View file

@ -5147,3 +5147,248 @@ fable-5: медиана ρ +0.52 · среднее +0.55 · глав согла
действительно прощает брак за прозу, то на масштабе он будет систематически ЗАВЫШАТЬ гладкий текст
с дефектами — то есть ровно тот исход, против которого продукт и строится (класс А). Ошибка в эту
сторону дороже экономии на судье.
---
## Д35 — ПЕРЕЕЗД МАШИНЫ: ЧТО ОН СЛОМАЛ В ПРИБОРАХ ФАЗЫ, И ДВЕ ЭРРАТЫ, НАЙДЕННЫЕ ПРИ ВОССТАНОВЛЕНИИ
Сессия 29.08 открылась не с замера, а с того, что **ни один риг фазы не запускался**: на новой
машине нет ни `~/books`, ни `eval/.venv`. Восстановление дало две находки, обе — не про эту фазу,
а про то, чем фаза меряет.
### Д35.1 ОКРУЖЕНИЕ ВОССТАНОВЛЕНО, И ОТКЛОНЕНИЯ ОТ СТЕНДА НАЗВАНЫ
* **Книги.** С 24.08 они лежат в `<репозиторий>/books` и версионируются ОТДЕЛЬНЫМ git-репозиторием
(`CLAUDE.md`), а все риги полигона ходят по `Path.home()/"books"`**65 вхождений в 31 файле**.
Поднято симлинком `~/books → <репозиторий>/books`. ⚠ Это восстановление контракта путей, а не
починка: следующая чистая машина упрётся в ту же стену, пока корень книг не станет одной
разрешающей функцией. Долг назван здесь, чтобы не всплыл как «риг сломан».
* **Интерпретатор.** `eval/.venv` пере-собран, `eval/requirements.txt` поставлен целиком.
**CPython 3.14.7 против пина `.python-version` 3.14.4** — патч-расхождение, названо, не
спрятано. Риги `dovodka/` — чистый stdlib (сверено импортами), поэтому расхождение на их
арифметику не влияет; для рига, зовущего вендора, это была бы другая оценка.
* Селфтесты после восстановления: `rol --selftest`, `money_d --selftest`, `runs --selftest`,
`sud --axis=d4 --selftest`, `naklon --selftest`, `gain --selftest`, `adjud --controls`**зелены**.
### Д35.2 ⛔ ПЕРЕЕЗД ОБНУЛИЛ mtime, И ОДИН ГЕЙТ ФАЗЫ БОЛЬШЕ НЕ ПЕРЕ-СНИМАЕТСЯ
`eval/tenant_panel/verify22.py:174-185` выводит верхнюю границу двойной оплаты Ф2 как «сумма
клеток, купленных ДО коммита атомарного замка `fd3e522`», и отделяет «до» от «после»
**временем файла на диске**. После переезда **все 335 клеток Ф2 имеют ОДИН И ТОТ ЖЕ mtime**
(проверено: `уникальных mtime: 1`), причём поздний. ⇒ граница считается как `$0.000000`,
не совпадает с напечатанным в отчёте эксп-22 числом, и **R71 реестра требований падает**.
**Диагноз, а не зелень.** Числа в отчёте эксп-22 верны — они сняты, когда mtime ещё существовали.
Утрачена **улика**, а не результат. Клетки времени покупки в себе не хранят (`dv-*.json`:
`arm · uid · model · finish · *_tokens · cost_usd · latency_s` — поля `ts` нет), значит граница
из сырья больше НЕ ВЫВОДИМА ничем.
**R71 объявляется НЕ ПЕРЕ-СНИМАЕМЫМ на этой машине.** Гейт не правится под зелень (D39.121):
подгонка спрятала бы ровно тот факт, что провенанс покупок держался на файловой системе.
**Класс шире одного гейта:** `runs.py:166` и `role_topology/absjudge.py:323` атрибутируют
судью ответу тем же способом — по mtime файла ответа. Для БУДУЩИХ чтений это работает (файлы
пишутся сейчас), для исторических — атрибуция утрачена там же и тогда же.
**Норма на будущее: время покупки и время ответа писать ПОЛЕМ В АРТЕФАКТ.** Файловая система —
не журнал.
### Д35.3 ⛔⛔ КАКОЕ СИСТЕМНОЕ СООБЩЕНИЕ ВЫЖИВАЕТ У GEMINI — УСТАНОВЛЕНО. ВЫЖИВАЕТ ПОСЛЕДНЕЕ
`00-provider-quirks.md:157` оставлял вопрос открытым дословно: «⚠ КАКОЕ именно выживает — НЕ
УСТАНОВЛЕНО: форум Google сообщает „выживает последнее“, а наш замер даёт 535 токенов ≈ размер
одного user-сообщения, то есть не выжило НИ ОДНО. Расхождение открыто».
**Закрыто арифметикой уже купленных клеток, за $0 и детерминированно.** У арма `RE` на провод
уходят ТРИ сообщения: `system[0]` — ролевой промт, **7 525 знаков, побайтно один и тот же на всех
16 главах**; `system[1]` — глоссарий, **255404 знака, РАЗНЫЙ на каждой главе** (термины берутся
из текста этой главы); `user` — исходник. Карантинные клетки — тот же вызов, у которого вендор
что-то выбросил.
```
RE.prompt_tokens RQ.prompt_tokens = 2436 на КАЖДОЙ из 16 глав, sd = 0.00
```
Разность **строго постоянна**. Выброшенное сообщение обязано быть постоянного размера — а
постоянен из двух системных только `system[0]`. Будь выброшены оба, разность росла бы вместе с
глоссарием (размах 149 знаков ≈ 50 токенов) и постоянной быть не могла.
**вендор выбросил ПЕРВОЕ системное и оставил ПОСЛЕДНЕЕ.** Форум Google был прав, наша прошлая
интерпретация — нет. (Подтверждение с другой стороны: `prompt_tokens` карантинных клеток коррелируют
с длиной ГЛОССАРИЯ +0.52 и с длиной исходника +0.04 — то есть внутри них глоссарий физически есть.)
**Почему прошлый вывод разошёлся с фактом.** Проба 22.08 сравнила 535 токенов с «размером одного
user-сообщения» на глаз. 2 436 токенов на 7 525 знаков русского — это 0.324 ток/знак; при такой
шкале глоссарий в 300 знаков весит ~100 токенов и в оценке «на глаз» просто теряется.
**⛔ И это меняет вывод про ДВИЖОК в худшую сторону.** `backend/internal/pipeline/render.go:247-258`
кладёт первым `system`**сам промт стадии**, вторым `system` — инъекцию банка (глоссарий/STM).
Записанное следствие звучало как «эскалационный хоп на Gemini тихо теряет глоссарий»
(`00-provider-quirks.md:158`). По установленному правилу всё наоборот:
> **на эскалационном хопе в Gemini при непустой инъекции банка движок теряет НЕ глоссарий, а ВЕСЬ
> ПРОМТ СТАДИИ, и оставляет глоссарий.**
То есть модель получает список канонных форм и текст — без единого слова о том, что с ними делать.
Ровно это и произошло с 16 клетками `RE`, и ровно так они выглядят: перевод есть, `finish=stop`,
русский, правильной длины — и никакой инструкции за ним. **Тихая потеря промта не отличима от
нормального ответа ничем, кроме счётчика входных токенов.**
⇒ Пинг оркестратору (зона бэкенда): лечение прежнее — склейка системных под этого вендора, — но
**приоритет и формулировка дефекта в бэклоге неверны и подлежат правке**.
---
## Д36 — ⭐ ШАГ 4 ПЛАНА 22.08: ДУЭЛЬ «РОЛЕВОЙ ПРОМТ ПРОТИВ ЕГО ОТСУТСТВИЯ». ПРЕ-РЕГ, ЗАПИСАН ДО СБОРКИ ПАКЕТОВ
**Зачем этот шаг вообще есть.** Фаза произвела много «неразличимо»: вся середина архитектурной
панели, все ступени размышления, семь вендоров из восьми. У каждого такого нуля есть паспорт с
порогом — но нет **шкалы**: неизвестно, какой перевес прибор в принципе способен развести на
промт-инженерном материале. Дуэль даёт эту шкалу природным экспериментом, который уже оплачен:
16 клеток одной модели на одних главах, где единственный варьируемый фактор — **есть ролевой
промт или его нет**.
⇒ Если даже полное исчезновение ролевого промта не даёт устойчивого знака — **у прибора нет
разрешения для промт-инженерных выводов вовсе**, и это печатается рядом с каждым «не хуже» дуги
Д23Д25 (экзамен на смелость `RN`/`RC`/`RB`, абляция). Если даёт — все прошлые нули получают
масштаб: «меньше, чем ВЕСЬ промт, во столько-то раз».
### Д36.0 ЧТО ТАКОЕ АРМ `RQ` — ТОЧНАЯ ФОРМУЛИРОВКА, А НЕ «БЕЗ ПРОМТА»
По Д35.3 установлено: у карантинных клеток выпал **ролевой промт (7 525 знаков)**, а **глоссарий
остался**. ⇒ Арм называется точно: **«тот же `gemini-3.1-flash-lite`, тот же исходник, тот же
глоссарий — но БЕЗ ролевого промта»**. Формулировка «вообще без инструкции», стоявшая в плане
22.08 и в шапке `rol.FOREIGN["RQ"]`, **неточна и здесь исправляется**: 130 из 2 566 токенов
инструкции до модели доехали, и это ровно те токены, что несут канон.
**Следствие для чтения результата, объявленное ДО чисел:** дуэль меряет вес **ролевого промта**
(2 436 токенов из 2 566, то есть 95%), а не «всего промта». Разницу в 5% не назвать нулём заранее
нельзя — именно эти 5% несут класс «банк».
### Д36.1 СОСТАВ ПАНЕЛИ И ЗАМОРОЖЕННЫЙ СПИСОК ГЛАВ
**Тег `p4`, новый; существующие теги не пере-эмитируются.** Пять армов:
| арм | что это | роль в панели |
|---|---|---|
| `RQ` | gemini flash-lite, исходник + глоссарий, **ролевого промта нет** | испытуемый |
| `RE` | тот же gemini, тот же вызов, **промт доехал** | компаратор |
| `Z0` | боевая связка (продакшен) | половина шумового пола |
| `ZF` | её вторая генерация | вторая половина пола |
| `ZD` | голый черновик `deepseek-v4-flash` | **ГРУБЫЙ ДЕКОЙ** |
**⚠ ОТКЛОНЕНИЕ ОТ ПЛАНА 22.08, ОБЪЯВЛЯЮ ЕГО ЗДЕСЬ, А НЕ ПОСЛЕ.** План (§5, Шаг 4) назвал панель из
четырёх армов — `RE · RQ · Z0 · ZF`. Добавлен пятый, `ZD`. Основание — **норма рига R57**: декой
обязан быть в каждой судейской пачке, а паспорт панели-0 прямо записал свой изъян строкой
«грубого декоя НЕТ — разрешение держится только на близнецах». `ZD` на этот материал годится как
никакой другой: он **различимо последний в ОБОИХ кругах и у ОБОИХ судейских семейств**
(Д33.8: +4.12 при пороге 2.39, p=0.006; +4.92 при пороге 2.10, p=0.0005). Панель, где известно
плохой арм не оказался последним, аннулируется целиком.
**Цена отклонения, названная заранее:** шкала мест 15, а не 14, поэтому числа этой панели
**не сопоставимы напрямую** с панелью-0 и с 11-армовой `arch2`. Сопоставимы отношения внутри
панели — ради них декой и добавлен.
**16 английских глав (пересечение всех пяти армов, сверено ДО чтения — 16/16 у каждого):**
```
f2274720c9 · 53f1a12dff · b065a92dc0 · 26c3bff1d4 · 49ca859c94 · 100b088f71 · 197f98eb61
d3237e1dea · eefdade2c3 · 27cb3a7e69 · 3bd6481182 · 8e50448cea · 90a20cb443 · 001e6ac4eb
c3517980c7 · 5a8f48d24a
```
Это те же 16 глав, что у панели-0, — поэтому **эталон дефект-классов, снятый по исходнику вслепую
(`eval/dovodka/etalon-panel0.json`, sha256 `02a7352e…`, фриз коммитом `bedc39a` 22.08), применим к
этой панели без единой правки**, и счётный прибор достаётся даром.
### Д36.2 ГОДНОСТЬ МАТЕРИАЛА — СНЯТА ДО ПРЕ-РЕГА И ПЕЧАТАЕТСЯ ЦЕЛИКОМ
Проверки годности (не эндпойнты) сняты раньше этого текста, и это объявляется прямо:
```
RQ 16/16 finish=stop · prompt_tokens 468548 (медиана 515) · доля кириллицы 0.9951.000
медиана длины выхода 1 708 знаков · всего уплачено $0.01513
RE 16/16 finish=stop · prompt_tokens 2 9042 984 (медиана 2 951) · кириллица ≥0.997
медиана длины выхода 1 698 знаков
```
⇒ Материал годен: обе половины дуэли — законченные русские переводы сопоставимой длины.
**Гейт правильности файла — числом, а не именем** (`rol.FOREIGN["RQ"]["prompt_tokens"] = (300,900)`):
подложенный файл нормального арма роняет сборку, и это проверено селфтестом на больном входе.
### Д36.3 ⛔ ЧТО В ЭТОЙ ПАНЕЛИ НЕ УРАВНЕНО — НАЗЫВАЕТСЯ ДО ЗАМЕРА
1. **`RQ` и `RE` — родня**: одна модель, один вызов, одни главы. Читатель может опознать родство
текстуально, как опознавал `Z0`/`ZF`. ⇒ пол панели по паре `Z0`/`ZF` есть **НИЖНЯЯ граница**
порога, и то же смещение действует на пару `RQ`/`RE` — в СТОРОНУ занижения разрыва.
2. **Три вендора в одной панели**: `RQ`/`RE` — Google, `Z0`/`ZF` — DeepSeek (связка), `ZD`
`deepseek-v4-flash`. Межармовые сравнения через границу вендора несут вендор-эффект.
3. **Потолки вывода не уравнены** (у `RQ`/`RE` `max_tokens` 32 000, у армов связки — свои).
4. **Размышление**: у `gemini-3.1-flash-lite` уровень **НЕ УСТАНОВЛЕН** (вендор не публикует
дефолт для этого слага, `00-provider-quirks.md:55`), и `reasoning_tokens=0` у Gemini —
норма поля, а не измерение. ⇒ конфигурация испытуемого и компаратора идентична друг другу, но
в абсолютных терминах неизвестна. Для дуэли это не важно (фактор один), для переноса — важно.
5. **Объём инструкции играет ЗА `RQ`**: у него на входе в 5.7 раза меньше токенов. Если он
проиграет — вывод сильнее объявленного; если выиграет — слабее.
### Д36.4 ДВА ПРИБОРА И ПЕРВИЧНЫЙ ЭНДПОЙНТ
**Прибор 1 — счётный, детерминированный.** `eval/dovodka/schet.py` (sha256 `1e292d1d…`, фриз
`bedc39a`) на четырёх классах: **род · язык · приём · банк**. Детекторы, эталон и правила
классификации **заморожены с 22.08 и в этой сессии не тронуты ни на строку** — это то же условие,
на котором стоял вердикт панели-0.
**Прибор 2 — ранговый.** Слепое чтение, **`claude-fable-5`, модель задаётся ЯВНО при запуске и
сверяется по транскриптам ПОСЛЕ прогона** (решение Д34.2; заявление о намерении моделью не
является — урок Д33.6). Обёртка `eval/dovodka/prompts/reader-wrapper.md`, sha256 `15cfac60…`,
та же, что во вторых чтениях и в пере-суде панели-0. По одному читателю на главу, 16 новых сессий.
**⭐ ПЕРВИЧНЫЙ ЭНДПОЙНТ — СИСТЕМАТИЧЕСКИЙ ЗНАК КОНТРАСТА `RE ↔ RQ` ПО ГЛАВАМ,** парный знаковый
тест, p<0.05 двусторонний. Так велел план: «Решает систематический знак, а не величина разрыва».
Потолок значимости при n=16 — 2/2¹⁶, дизайн мощности не лишён; при 12 главах в одну сторону
p≈0.077, при 13 — p≈0.021, то есть **для вывода нужно не менее 13 глав из 16 в одну сторону**
(при отсутствии связок). Это печатается ДО чисел.
**Вторичные:** (i) величина разрыва в местах против собственного порога панели (конъюнкция:
|Δ|>порог И p<0.05); (ii) те же четыре дефект-класса счётным прибором, парный знаковый;
(iii) контроль пола `Z0`/`ZF`; (iv) контроль декоя — `ZD` обязан оказаться последним.
**Порог** считается собственным полом панели (пара `Z0`/`ZF`), как в Д32 и Д34; априорный MDE
рангового прибора при n=16 и k=5 держится около двух мест — поэтому **величина разрыва объявляется
ОПИСАТЕЛЬНОЙ, а несущим остаётся ЗНАК**.
### Д36.5 ⛔ ПРАВИЛО РЕШЕНИЯ, ОБЪЯВЛЕННОЕ ДО ЧИСЕЛ. ВСЕ ТРИ ИСХОДА
* **`RE` систематически лучше `RQ` (знак есть, p<0.05).** У прибора ЕСТЬ разрешение на
промт-инженерном материале, и вес ролевого промта известен в местах. Тогда каждое «неразличимо»
дуги получает шкалу: «эффект меньше, чем весь ролевой промт, во столько-то раз».
* **Знака нет (p≥0.05).****Худший исход, и печатать его придётся первым.** Он означает, что
прибор не разводит даже полное исчезновение промта, ⇒ **ни один промт-инженерный вывод фазы
(экзамен на смелость `RN`/`RC`/`RB`, абляция Д21, «роль лучше склейки») не имеет под собой
разрешения**, и это сносится в каждую строку, где такой вывод стоит.
* **`RQ` систематически ЛУЧШЕ `RE`.** Промт ВРЕДИТ на этом жильце. Печатается первым и отдельно;
для продукта это означает, что ролевой промт, писанный под сильную модель, на слабой работает
против неё — и тогда «промт — часть арма» надо читать как «промт — часть ПАРЫ модель×промт».
**Что этот шаг НЕ закрывает, объявляю заранее:** он не переносится на другого жильца (один
`gemini-3.1-flash-lite`), не переносится на китайскую пару, и не говорит ничего об оси ВЕРНОСТИ
владельца (Д30). Он даёт шкалу ОДНОГО прибора на ОДНОМ жильце.
### Д36.6 ⭐ ПРЕДСКАЗАНИЕ, КОТОРОЕ МОЖНО ОПРОВЕРГНУТЬ, И ОНО ЗАПИСАНО ДО СЧЁТА
Из Д35.3 следует не только формулировка арма, но и **проверяемое предсказание счётного прибора**:
> глоссарий у `RQ` ЕСТЬ, ролевого промта НЕТ ⇒ по классу **«банк»** армы `RQ` и `RE` должны
> оказаться БЛИЗКИ, а расходиться обязаны классы, которые несёт ролевой промт: **«язык»**
> (мандат «выведи ТОЛЬКО перевод», правила передачи французского слоя, запрет латиницы на именах)
> и **«приём»** (в промте о типографике не сказано ничего — предсказываем ноль различия),
> плюс проза, которую счётный прибор не видит вовсе.
⛔ **Если по классу «банк» окажется большая разница — интерпретация Д35.3 неверна, и её надо
пере-открывать, а не подгонять.** Предсказание записано именно для этого.
### Д36.7 ПРОЦЕДУРА И СВЕРКИ ДО ЧТЕНИЯ
1. `rol.py --emit-read en --panel=RQ,RE,Z0,ZF,ZD --tag=p4 --each --n=16` — один пакет на главу.
2. `rol.py --verify-read en --tag=p4` — побайтная сверка каждого текста с клеткой своего арма,
исходник главы, раскладки, имена армов в пакете, побайтно совпадающие армы, гейт длины.
⚠ EXIT=0 сертификатом НЕ является (норма плана §6): вывод читается ГЛАЗАМИ.
3. Ключ эмитируется ДО ответов, читателю не даётся; в пакете нет ни имён армов, ни строки
«БЕЗ-ПРОМТА», ни вендоров, ни слова «однопроходка».
4. `runs.py --claim p4 en <uid8>` ДО запуска каждого читателя; модель — параметром запуска.
5. Аудит транскриптов ПОСЛЕ: поле `model` у всех 16 агентов обязано быть `claude-fable-5`.
6. `READERS-p4.json` пишется **ДО** прогона (долг Д33.6 закрывается по норме, а не задним числом).

View file

@ -492,11 +492,15 @@ def _e6_wire(uid: str) -> tuple[list[dict], str, dict]:
def _rq_wire(uid: str) -> tuple[list[dict], str, dict]:
"""WIRE-АНАЛОГ `RQ` — ЗАПРОС В ТОЙ ФОРМЕ, В КОТОРОЙ ОН УХОДИЛ ДО ПОЧИНКИ СКЛЕЙКИ.
Клетки `RQ` это `RE`, у которого вендор МОЛЧА ВЫБРОСИЛ второе системное сообщение:
OpenAI-совместимый слой Gemini принимает ровно одно. Поэтому здесь НЕ зовётся `fit_msgs`
Клетки `RQ` это `RE`, у которого вендор МОЛЧА ВЫБРОСИЛ ПЕРВОЕ системное сообщение
ролевой промт, и оставил ПОСЛЕДНЕЕ, то есть глоссарий: OpenAI-совместимый слой Gemini
принимает ровно одно. Прежняя редакция этого комментария говорила «второе», и это было
неверно: разность `RE.prompt_tokens - RQ.prompt_tokens` равна 2436 РОВНО на всех 16 главах,
а постоянного размера из двух системных только первое (эррата Д35.3). Поэтому НЕ зовётся `fit_msgs`
(склейка, заведённая после находки) и НЕ зовётся `rol.call_kwargs`: печатается ровно то, что
отправлял код 21.08. Печатается ОТПРАВЛЕННОЕ, а не ПОЛУЧЕННОЕ моделью второе нам недоступно
по построению, и разница видна только по счётчику: 515 токенов входа против 2951 у `RE`.
Из этих 515 глоссарий составляет ~100 арм есть «без РОЛЕВОГО промта», а не «без промта».
"""
src = next(u["source"] for u in chosen("en") if u["uid"] == uid)
msgs = rol_msgs("en", src, "RE")
@ -507,9 +511,9 @@ FOREIGN = {
"E6": {"pair": "en", "prefix": "dv-g-e6-", "model": "glm-5", "role": "editor3",
"phase": "ФД-G", "rig": "eval/dovodka/en_axis.py --e6", "wire": _e6_wire,
"что": "ВТОРОЙ редактор glm-5 поверх боевого черновика D0 (носитель гипотезы H-4)"},
# ⚠ ПСЕВДО-АРМ ПОД КАРАНТИННЫМ СУФФИКСОМ. 16 клеток `RE`, которые вендор перевёл БЕЗ ИНСТРУКЦИИ:
# OpenAI-совместимый слой Gemini принимает РОВНО ОДНО системное сообщение и молча выбрасывает
# лишние. Деньги за них списаны, тексты годны, `finish=stop` 16/16 — это оплаченный и
# ⚠ ПСЕВДО-АРМ ПОД КАРАНТИННЫМ СУФФИКСОМ. 16 клеток `RE`, у которых до модели НЕ ДОЕХАЛ РОЛЕВОЙ
# ПРОМТ: OpenAI-совместимый слой Gemini принимает РОВНО ОДНО системное сообщение, выбрасывает
# ПЕРВОЕ и оставляет ПОСЛЕДНЕЕ — то есть глоссарий доехал, а инструкция нет (Д35.3). Деньги за них списаны, тексты годны, `finish=stop` 16/16 — это оплаченный и
# незапланированный природный эксперимент «сколько весит ВЕСЬ промт».
# ⛔ ИМЯ НЕЙТРАЛЬНОЕ И СУФФИКСА В ПАКЕТЕ БЫТЬ НЕ ДОЛЖНО: читатель, увидевший «БЕЗ-ПРОМТА»,
# мерил бы подсказку. `arm_field` = "RE", потому что в самой клетке написано «RE» — она и есть
@ -521,7 +525,8 @@ FOREIGN = {
"model": "gemini-3.1-flash-lite", "role": "onepass", "arm_field": "RE",
"phase": "ФД-N", "rig": "eval/dovodka/rol.py --buy en RE (клетки 21.08, карантин)",
"prompt_tokens": (300, 900), "wire": _rq_wire,
"что": "тот же gemini БЕЗ ИНСТРУКЦИИ — вендор выбросил системное сообщение"},
"что": "тот же gemini БЕЗ РОЛЕВОГО ПРОМТА — вендор выбросил первое системное, "
"глоссарий доехал"},
}

111
eval/dovodka/sysmsg.py Normal file
View file

@ -0,0 +1,111 @@
#!/usr/bin/env python3
"""КАКОЕ СИСТЕМНОЕ СООБЩЕНИЕ ВЫЖИВАЕТ У GEMINI — ДЕТЕРМИНИРОВАННАЯ УЛИКА. $0.
ЗАЧЕМ ОТДЕЛЬНЫЙ ФАЙЛ. Вопрос стоял открытым в `docs/experiments/00-provider-quirks.md`
дословно: «КАКОЕ именно выживает НЕ УСТАНОВЛЕНО Расхождение открыто». Закрыт он не новой
живой пробой (она стоит денег и протухает вместе со слагом), а АРИФМЕТИКОЙ уже купленных клеток.
Такой вывод обязан иметь исполняемого носителя: иначе через месяц он останется утверждением в
отчёте, которое никто не может пере-снять.
ЛОГИКА ДОКАЗАТЕЛЬСТВА, и она не требует ни одного вызова модели:
у арма `RE` на провод уходят ТРИ сообщения
system[0] ролевой промт ПОБАЙТНО ОДИН И ТОТ ЖЕ на всех главах
system[1] глоссарий РАЗНЫЙ на каждой главе (термины берутся из текста главы)
user исходник разный
карантинные клетки `RQ` тот же вызов, у которого вендор что-то выбросил.
если разность `RE.prompt_tokens RQ.prompt_tokens` ПОСТОЯННА по главам, выброшено
сообщение постоянного размера, а таково только system[0] ролевой промт.
Будь выброшены ОБА системных, разность росла бы вместе с глоссарием и постоянной быть
не могла; будь выброшено только system[1], постоянной была бы не разность, а сам `RQ`.
ГЕЙТ ПАДАЕТ, ЕСЛИ ВЫВОД НЕ ДЕРЖИТСЯ. Он проверяет ТРИ вещи, и каждая может уронить:
1. system[0] действительно побайтно постоянен, а system[1] действительно варьирует
(иначе рассуждение неприменимо и это надо увидеть, а не предположить);
2. разность постоянна с точностью до нуля;
3. остаток `RQ` разложим на глоссарий + исходник: он обязан коррелировать с длиной ГЛОССАРИЯ
сильнее, чем с длиной исходника.
Запуск: eval/.venv/bin/python eval/dovodka/sysmsg.py
"""
from __future__ import annotations
import json
import statistics as st
import sys
from pathlib import Path
ZONE = Path(__file__).resolve().parent
sys.path.insert(0, str(ZONE))
import rol # noqa: E402
OUT = Path.home() / "books" / "dovodka"
def _corr(a: list[float], b: list[float]) -> float:
ma, mb = st.mean(a), st.mean(b)
num = sum((x - ma) * (y - mb) for x, y in zip(a, b, strict=True))
den = (sum((x - ma) ** 2 for x in a) * sum((y - mb) ** 2 for y in b)) ** 0.5
return num / den if den else 0.0
def main() -> int:
rows = []
for u in rol.chosen("en"):
uid = u["uid"]
pair = OUT / f"dv-n-re-{uid}.json", OUT / f"dv-n-re-{uid}.БЕЗ-ПРОМТА.json"
if not all(p.exists() for p in pair):
print(f"⛔ нет пары клеток для {uid[:10]} — улика неполна")
return 1
re_, rq_ = (json.loads(p.read_text(encoding="utf-8")) for p in pair)
msgs, _model, _kw = rol.FOREIGN["RQ"]["wire"](uid)
if [m["role"] for m in msgs] != ["system", "system", "user"]:
print(f"⛔ форма вызова не «system·system·user»: {[m['role'] for m in msgs]}")
return 1
rows.append({
"uid": uid[:10],
"re": re_["prompt_tokens"], "rq": rq_["prompt_tokens"],
"sys0": len(msgs[0]["content"]), "sys1": len(msgs[1]["content"]),
"user": len(msgs[2]["content"]),
})
print(f"{'глава':12}{'RE_pt':>7}{'RQ_pt':>7}{'RE-RQ':>7}{'sys0':>7}{'sys1':>6}{'user':>7}")
for r in rows:
print(f"{r['uid']:12}{r['re']:>7}{r['rq']:>7}{r['re'] - r['rq']:>7}"
f"{r['sys0']:>7}{r['sys1']:>6}{r['user']:>7}")
bad = 0
sys0 = {r["sys0"] for r in rows}
sys1 = {r["sys1"] for r in rows}
diff = {r["re"] - r["rq"] for r in rows}
def ck(ok: bool, what: str) -> None:
nonlocal bad
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {what}")
bad += 0 if ok else 1
ck(len(sys0) == 1, f"ролевой промт постоянен по главам: вариантов длины {len(sys0)} ({sys0})")
ck(len(sys1) > 1, f"глоссарий по главам РАЗНЫЙ: вариантов длины {len(sys1)}")
ck(len(diff) == 1, f"разность RERQ постоянна: вариантов {len(diff)} ({diff})")
d = next(iter(diff)) if len(diff) == 1 else -1
tok_per_char = d / next(iter(sys0)) if len(sys0) == 1 and d > 0 else 0.0
c_gl = _corr([float(r["rq"]) for r in rows], [float(r["sys1"]) for r in rows])
c_src = _corr([float(r["rq"]) for r in rows], [float(r["user"]) for r in rows])
ck(c_gl > c_src, f"остаток RQ идёт за ГЛОССАРИЕМ, а не за исходником: "
f"corr {c_gl:+.2f} против {c_src:+.2f}")
print(f"\nвыброшено {d} токенов на {next(iter(sys0))} знаков ролевого промта "
f"= {tok_per_char:.3f} ток/знак русского текста")
print("⇒ ВЫЖИВАЕТ ПОСЛЕДНЕЕ СИСТЕМНОЕ, ВЫБРАСЫВАЕТСЯ ПЕРВОЕ."
if not bad else "\n⛔ ВЫВОД НЕ ДЕРЖИТСЯ — пере-открывать, а не подгонять.")
print("⚠ Это утверждение о ПОВЕДЕНИИ СЛОЯ на дату покупки клеток (21.08.2026). "
"Слаг ≠ модель, поведение слоя тоже имеет срок годности.")
return 1 if bad else 0
if __name__ == "__main__":
sys.exit(main())