Freeze phase D pre-registration: hypotheses verbatim, Holm-corrected power with declared families, equivalence margin, measured budget, cash desk, ja pair pack

This commit is contained in:
Claude (backend session) 2026-08-10 07:06:07 +03:00
parent b1780b8d9d
commit da5f4d0224
12 changed files with 2558 additions and 1 deletions

View file

@ -0,0 +1,896 @@
# 23. Сильный тир редакторской роли и граница `glm-5`
**Полигон-сессия, 09.08.2026.** Номер 23 присвоен оркестратором №16 при лендинге 10.08. Зона:
`eval/editor_tier/` + этот файл. Санкция владельца на потолок пака $4.00 (09.08).
> ⚠⚠ **РЕВЬЮ-ШАПКА ПРИЁМКИ (оркестратор №16, 10.08) — шапка первична над телом.** Числа пака
> подтверждены независимым пересчётом мимо харнесса (border/tier/деньги/репликация/внешний судья —
> до знака; LOO по сессиям устойчив; аннулированные прогоны воспроизводятся из сырья, «фантомные»
> 0.29/0.42 подтверждены невыводимыми). **Мандат пака, потолок $4.00 и санкции подтверждены
> владельцем задним числом 10.08.** **Выводы НЕ ратифицированы** — владелец признал постановку
> неполной и заказал **фазу Д** (`docs/POLYGON_EXP2223_REDO_SESSION_PROMPT.md`): добивка
> gemini · внешняя подпись `tier` · несущие оси en/ja · edit-контур · канон-вскрытие; отчёт фазы —
> секции Д0Д8 этого файла. Поправки приёмки к телу (чек-лист §Д8 промта), главные: счёт
> агент-сессий = **58**, не 50 (§6/§14) · противоречие §9/§13 против §12в о внешнем контуре ·
> наклон `border` по текущему сырью **+0.708**, не +0.646 · боевые `aj-border*` — пере-штампованные
> копии `replication-runA` (mtime 23:31:54), исходный mtime-провенанс судейства утрачен · в голосах
> `replication-runB` — judge-имена прогона A (`ingest` читает JUDGES-файл прогона A).
> ⚠ **ЗАКАЗА НА ЭТОТ ПАК НЕ СУЩЕСТВУЕТ.** Промта на эксп-23 в `docs/` нет: пак назначен сессией
> себе самой, владелец санкционировал только ДЕНЬГИ. Оркестратор уже пометил его как незаявленный.
> Пре-рег и реестр требований, на которые ссылается этот отчёт, написаны той же сессией и заказом
> не являются — читать их как самообязательство, а не как контракт. Содержательно пак закрывает
> дыру, объявленную самим эксп-22 (§13а: сильный тир был срезан из панели), но и она была не
> заданием, а его находкой. Список к подписи владельца — §14.
> **СТАТУС: замер закончен.** Потрачено **$2.907359** при пакетном потолке $4.00; судейство обоих
> проходов пере-снято после приёмки (`tier` — из-за отсутствия декоя, `border` — из-за подменённой
> базы арма, §0 и §3), пере-прогоны покупок не потребовали.
> Числа пере-считываются `eval/editor_tier/itog23.py` из персистированного сырья и сторожатся
> `eval/editor_tier/verify23.py` — ненулевой код возврата значит «отчёт не сдаётся».
>
> ⚠ **Чего гейты НЕ доказывают, вопреки прежней редакции этой шапки.** (1) Гейт чисел сторожит,
> что число ПРИСУТСТВУЕТ в тексте, а не что предложение вокруг него истинно; пороговые и
> сравнительные утверждения проверяются только чтением. (2) `eval/conformance.py --final` гонялся
> против реестра требований, который живёт в сессионном scratchpad и в репозиторий не попадает, —
> читатель его пере-снять не может, и «соответствие заказу проверено» отсюда не следует; вдобавок
> большая часть его улик — греп формулировок ЭТОГО ЖЕ отчёта, то есть отчёт проверял сам себя.
> (3) Прежняя редакция `verify23.py` пропускала проход, по которому нет ни одного голоса, и
> печатала «все числа сходятся» с нулём проверок — ровно так аннулированный `border` прошёл гейт.
> Починено; но каждый из трёх дефектов прошлые редакции этой шапки называли доказательством.
## ИТОГ — ВХОД ДЛЯ РЕШЕНИЯ ВЛАДЕЛЬЦА
**ЧИТАТЬ §0 ПЕРВЫМ.** Судейство этого пака аннулировалось ДВАЖДЫ, по двум разным причинам:
первый прогон — потому что в нём не создавался декой; второй, в части прохода `border`, — потому
что у арма `R0` на половине единиц была подменена база. Оба дефекта нашла приёмка, не автор.
Ниже — числа третьего, принятого судейства: оба прохода с полным набором контролей, декой пойман
в 16/16 и 32/32 единиц, отбраковок ноль.
**Оба несущих ответа пака отрицательные, и это не осторожность формулировки, а результат.** Ни
сильный тир четырёх провайдеров, ни пограничный `glm-5` не отличимы от боевого редактора
`deepseek-v4-pro` на пороге, который кладёт собственный шум прибора. Рекомендация эксп-22 остаётся
в силе — теперь на панели, где сильный тир присутствует, а не срезан. По правилу ничьей D39.117
это тем более так: `deepseek-v4-pro` из неразличимых ещё и самый дешёвый.
### 0. ДВА АННУЛИРОВАННЫХ ПРОГОНА И ЧЕМУ ОНИ УЧАТ
Хук декоя возвращал ригу ИМЯ арма вместо ТЕКСТА; порча садилась в строку «R0», давала 0 замен при
пороге 2 и декой МОЛЧА не создавался — ноль контролей в обоих проходах. Нашло это адверсариальное
ревью, которому было поручено искать вне карты отчёта; сам отчёт при этом ссылался на правило
«сессия без пойманного декоя аннулируется».
**Один и тот же оплаченный материал дал ПРОТИВОПОЛОЖНЫЕ ответы:**
```
контраст без декоя (run1, аннулир.) с декоем (run2, ТОЖЕ АННУЛИРОВАН) принято (run3)
T2 gpt-5.6-terra +2.00 «БЬЁТ» +0.50 ниже порога +0.50
T3 grok-4.5 +1.81 «БЬЁТ» +0.19 ниже порога +0.19
R2 glm-5 0.09 «эффекта нет» 1.53 «ЗНАЧИМО ХУЖЕ» 0.81 ниже порога
```
⚠ **Поправка фазы Д (чек-лист приёмки №16 п.7): прежде колонка называлась «с декоем (принято)», и
в строке `R2` под этим заголовком стояло 1.53 — число АННУЛИРОВАННОГО run2.** Принятые числа
`tier` (`T2`/`T3`) действительно из run2, а принятое число `border` — из run3, потому что run2
аннулирован именно по проходу `border` (подменённая база `R0`, §3). Один заголовок на две колонки
разного статуса и порождал ошибку; теперь статус подписан у каждой.
**Однофакторной эта таблица является только по строке `border`.** В проходе `tier` между двумя
прогонами изменилось ДВА: появился декой И появился позитивный контроль `F` (ключи: армы run1
`[R0,T1,T2,T3]`, семейство из 3 строк · принятый `[R0,T1,T2,T3,F]`, семейство из 4). Делитель
Холма изменился 3→4, но ни один вердикт от этого не двигается. Чистая улика — `border`: там состав
армов совпадает и добавлен ровно декой. Кроме того обе пачки судили разные, нигде не
зафиксированные популяции судей (см. §8), так что «менялось только X» вообще не доказуемо этим
дизайном — правильная формулировка «наблюдаемое различие сопровождало появление декоя».
⚠ **Строка `border` в этой таблице — улика ПРО ДЕКОЙ, но ни одно её число не является ответом пака
про `glm-5`.** Оба сравниваемых прогона несли один и тот же дефект базы `R0` (§3): он в сравнении
удерживается постоянным, поэтому разница 0.09 против 1.53 по-прежнему говорит о влиянии декоя, —
но правильный ответ снят третьим прогоном и равен 0.81. Не смешивать два утверждения: «декой
меняет вердикты» пак показал, «вердикт по `glm-5` такой-то» — только в §3.
**Урок шире декоя.** Каждый из трёх прогонов давал внутренне связную таблицу, и каждый раз
неверным оказывался НЕ вывод из чисел, а само основание под числами: сперва отсутствующий контроль,
потом подменённая база. Ни один из двух дефектов не был виден в результатах — оба нашлись только
чтением кода и сверкой сырья. Отсюда практическое правило, а не мораль: пока контроль не напечатан
числом рядом с боевым перевесом, «результат» — это гипотеза о работе прибора, а не о моделях.
Тексты не менялись, деньги не тратились — менялось наличие заведомо испорченного текста в
судейской пачке. **Механизм:** декой задаёт судье масштаб настоящей ошибки. Без него судья
сравнивает хорошее с хорошим и раздувает мелочь до величины реальной разницы (отсюда ложные +2.00)
либо, наоборот, не находит разницы там, где она есть (отсюда ложный ноль). Косвенное подтверждение:
в аннулированном прогоне половина единиц отбраковывалась за ненулевые оси БЕЗ цитат — судьям было
нечего цитировать, они регистрировали различия, а не ошибки. В пере-прогоне отбраковка — ноль.
**Одна отсутствующая проверка дала две ошибки РАЗНОГО знака.** Контроль чувствительности судьи —
не формальность протокола, а условие осмысленности любого числа пака.
### 1. СИЛЬНЫЙ ТИР НЕ ОТЛИЧИМ ОТ БОЕВОГО РЕДАКТОРА
16 единиц, побайтно одна якорная база, Холм по четырём. Порог различимости — шумовой пол СВОЕГО
прохода, **1.02**.
```
T1 glm-5.2 0.19 [0.56, +0.12] Холм 1.0000 ниже порога различимости
T2 gpt-5.6-terra +0.50 [0.06, +1.12] Холм 0.5977 ниже порога различимости
T3 grok-4.5 +0.19 [0.12, +0.62] Холм 1.0000 ниже порога различимости
R0 vs F (КОНТРОЛЬ) +2.06 [+1.19, +3.00] Холм 0.0039 ✔ боевой редактор ПОКУПАЕТ поверх черновика
```
**Формулировка выбрана буквально.** Вердикт пре-рега — «ниже порога различимости при данном n», а
не «различий нет»: у `T2` интервал доходит до +1.12, то есть накрывает значения ВЫШЕ порога 1.02.
Утверждать равенство армов эти данные не позволяют — они позволяют утверждать, что эффект меньше
того, что прибор на 16 единицах способен развести.
**Позитивный контроль — ключ к чтению.** Прибор находит +2.06 там, где разница реальна (редактор
против голого черновика), и не находит ничего между редакторами. Значит малость перевесов — это
свойство армов, а не слепота прибора. Контроль `F` добавлен ПОСЛЕ аннулирования первого прогона:
он впечатан в ключ до появления первого ответа судьи (пре-регистрация соблюдена), и на вердикты
T1T3 не влияет — при семействе из 3 и из 4 поправка Холма даёт одни и те же величины.
**Посылка 1 ПОДТВЕРЖДЕНА: дороговизна качества не покупает.** Сильный тир четырёх провайдеров
не даёт различимого выигрыша над `deepseek-v4-pro`.
### 2. ВЫВОД ЭКСП-22 УСТОЯЛ И ТЕПЕРЬ ПОДПЁРТ ПОЛНОЙ ПАНЕЛЬЮ
Эксп-22 рекомендовал `deepseek-v4-pro` и сам объявил (§13а), что вывод верен лишь ВНУТРИ панели,
потому что сильный тир был срезан. Дыра закрыта: тир проверен и не лучше. **Рекомендация остаётся,
и теперь она стоит на панели, включающей сильный тир OpenAI, xAI и Z.AI.**
### 3. ГРАНИЦА `glm-5` НЕ РАЗРЕШЕНА: ЭФФЕКТ НИЖЕ ПОРОГА РАЗЛИЧИМОСТИ
32 единицы (16 эксп-22 + 16 новых), порог — шумовой пол СВОЕГО прохода, **1.48**.
```
R2 glm-5 vs R0 боевой 0.81 [1.81, +0.28] Холм 0.1601 ниже порога различимости
контроли: ДЕКОЙ 4.88 поймано 32/32 ✔ · ПОЛ 1.00 [2.06, +0.06] честен · побайтных дублей нет
```
**Это ТРЕТЬЕ судейство прохода; первые два аннулированы.** Второе давало 1.53 [2.19, 0.84],
Холм 0.0002, и на нём стояли вывод «граница разрешена ПРОТИВ `glm-5`» и закрытие Резерва D39.22.
Приёмка нашла в нём подменённую базу: на 16 из 32 единиц арм `R0` был редактурой боевого редактора
поверх ЧУЖОГО черновика, а не поверх якорного, — контраст сравнивал армы над разными основаниями.
Причина в именовании эксп-22, где арм редактуры назван по ЧЕРНОВИКУ-жильцу, а не по редактору.
**Что изменилось в третьем прогоне** (покупок не потребовал, тексты те же): база `R0` сверена
побайтно с якорной редактурой эксп-22 — 16/16 · донор декоя уравнен по армам (было: порча во всех
32 единицах делалась из `R0`, а `R0` — второй арм ЕДИНСТВЕННОГО контраста прохода; стало 16 из
`R0`, 16 из `R2`) · идентичность судейских сессий персистирована (прежде во всех голосах стоял
`judge='?'`) · пере-суживание довело выборку до полных 32 единиц, отбраковок ноль.
**Проверка, что уравнивание донора не подменило вывод:** на 16 единицах с донором `R0` перевес
0.81 и на 16 с донором `R2` тоже 0.81 — вклад донора неотличим от нуля.
**Поправка фазы Д: `p` пере-считаны ТОЧНО, а были оценкой.** При n>20 риг брал Монте-Карло
(200 000 розыгрышей, фиксированное зерно) — отсюда одна и та же величина печаталась в отчёте как
0.1603 и как 0.1608, то есть заявление «числа воспроизводятся до знака» на ней не выполнялось.
Перевесы целые, поэтому распределение суммы ±xᵢ берётся динамикой по достижимым суммам ТОЧНО и
мгновенно: **p = 0.1601** (боевой прогон и `runA`), **0.3339** (`runB`). Ни один вердикт не
двигается; двигалась только третья значащая цифра, и теперь она воспроизводима.
**Расщепление по половинам** (обе половины на правильной базе):
```
все 32 единицы 0.81 p 0.1601
16 старых эксп-22 1.62 p 0.0449
16 новых 0.00 p 1.0000
```
Половины расходятся: на старых 16 единицах перевес почти достаёт до порога, на новых он ровно ноль.
Ни одна не проходит порог 1.48, но расхождение половин само по себе больше, чем удобно объяснять
шумом, и различаются они ещё и составом глав. Это и есть граница того, что пак может сказать.
Прежняя интерпретация — «прежний результат ВЕРЕН, а не артефактом малого n» — не воспроизвелась:
контраст эксп-22 (2.12 на 16 единицах) при удвоении n и починенных контролях сжался до 0.81.
⇒ **Ожидание пре-рега оправдалось: перевес сжался, а не устоял.**
⚠ Что здесь НЕ утверждается. «Ниже порога» не значит «армы равны»: интервал тянется от 1.81 до
+0.28. Знак отрицателен во всех трёх прогонах и в эксп-22, то есть слабое свидетельство против
`glm-5` есть — на этом приборе и этом n оно не доходит до различимого. Разрешается граница только
ростом мощности (больше единиц или менее шумный судейский контур), а не пере-чтением этих чисел.
### 4. ЦЕНА (замер, 1500 единиц на книгу)
```
арм p50 edit p95 edit p50 связка книга p50 книга p95 × к R0
R0 deepseek-v4-pro 0.00769 0.01573 0.00897 $13.45 $25.13 —
T1 glm-5.2 0.01725 0.01955 0.01879 $28.18 $31.66 2.09×
T2 gpt-5.6-terra 0.04408 0.04841 0.04544 $68.17 $74.88 5.07×
T3 grok-4.5 0.05276 0.06441 0.05397 $80.96 $97.88 6.02×
якорный черновик (общая база связки, входит в каждую связку): p50 0.00122
```
**Прежняя редакция этой таблицы сравнивала связку с не-связкой.** В строке `R0` стояло
`0.00545` — медиана ДВУХ клеток скрина эксп-22, не этих шестнадцати, — а книжные столбцы при ней
были посчитаны по СВЯЗКЕ «черновик+редактор», тогда как у `T1``T3` те же столбцы шли по одной
редактуре. Нескладность была видна в самой строке: 0.00545 × 1500 = $8.18, а напечатано $13.45.
Ни один гейт её не трогал: пере-счёт спрашивал у эксп-22 метод `edit_cost`, которого у него нет, и
строка молча выходила нулевой. Починено в `itog23.py`/`verify23.py`; теперь оба семейства колонок
печатаются рядом и сторожатся. **Вывод «сильный тир в 26 раз дороже» от починки не изменился**
он и считался по связкам, просто напечатан был не тем числом.
**Решение однозначно и не требует продуктового суждения:** сильный тир стоит в 26 раз дороже и
не даёт различимого выигрыша. Платить не за что.
### 5. ОТКАЗНОЙ РЕЖИМ KIMI — КЛЕТКА ЗАМЕРЕНА ПОД СВОИМ ПОТОЛКОМ (посылка 2 согласуется, но не различает)
`kimi-k3` в редакторской роли — **пустой выход при 99.9% доли размышления**, $0.0804 за клетку.
Особый режим (одна клетка вместо полного скрина), объявленный ДО покупок, сэкономил ≈$1.
⚠ **Клетке был выдан СВОЙ потолок вывода: `max_out=4000` против 16000 у всех остальных**
(`screen.py:47`), и сырьё показывает `finish=length · completion 4000 · reasoning 3997 · content
пуст`. То есть модель уперлась в мой потолок, ещё не выйдя из фазы размышления. Прежняя редакция
называла только «одну клетку вместо полного скрина» и умалчивала о потолке, а квирк-реестр проекта
(`00-provider-quirks.md`) описывает ровно этот симптом как нехватку бюджета и предписывает ≥16000.
Поэтому: **вердикт по ЦЕНЕ твёрдый** — $0.080406 уже выше порога роли $0.06, а при 16000 клетка
стоила бы ≈$0.26; **вывода об отказном режиме вендора эта клетка не даёт** — она не различает
«модель отказывается работать» и «мой потолок обрезал её на размышлении». Эксп-22 снимал
`kimi-k2.6` другим замером; повторением того результата это считать нельзя. Девиация — в §8.
### 6. ДЕНЬГИ И ДИСЦИПЛИНА
```
ФA скрин $0.481038 / 1.10 ФC панель+пол $2.080125 / 2.30
ФB единицы $0.346196 / 0.45 ПАК $2.907359 / 4.00
```
Ни один потолок не пробит, деньги сведены двумя путями (расхождение ≤7e-6). Пере-прогон судейства
покупок не потребовал. Дополнительная санкция владельца на $5 не понадобилась.
⚠ **Потолок ФC поднимался ДВАЖДЫ; вот точная хронология по коммитам и mtime сырья.**
```
03:21:57 фриз f1f9947 ФC 1.80 → 1.95, объявлено в коде фазы
04:37:39 первая покупка панели ← 76 мин ПОСЛЕ фриза
05:17:43 последняя покупка панели ← панель фактически стоила $1.798638
05:36:25 фриз 1f6d6ae ФC 1.95 → 2.30
05:37:25 первая покупка шумового пола ← 60 с ПОСЛЕ фриза
```
Ни один доллар не потрачен под незафризенным операционным потолком. **Обе прежние редакции этого
абзаца были неверны, и во взаимно противоположные стороны:** сначала «поднят до покупок фазы» —
умалчивая, что подъёмов было два; затем «правка попадает в середину окна покупок» — а она в него
не попадает вовсе, второй подъём случился через 19 минут ПОСЛЕ последней покупки панели. Вторая
формулировка была самооговором, написанным при пере-чтении по памяти, без сверки с `git log`.
**Основание подъёма названо замером, а было проекцией.** «Панель $1.844» — это оценка
`--c-plan` по медианам цен фазы A, а фактическая панель стоила **$1.798638**, то есть влезала и в
исходные $1.80. Честное основание второго подъёма другое и его надо назвать прямо: своему шумовому
полу нужно было ещё $0.281487, и проекционный сторож кассы при потолке 1.95 отказал бы последним
клеткам. Взят подъём из объявленного планом резерва пака ($0.65), пакетный потолок $4.00 не
двигался. Расход резерва на СВОЙ шумовой пол — не то назначение, под которое резерв объявлялся
(«ре-гены эхо-мины и ретраи»), и правило пре-рега «не влезает — СТОП и пинг» исполнено не было:
артефакта пинга нет. Вопрос владельцу — §14.
**Позиционная поправка замерена и вычтена по ОБОИМ проходам.** `tier`: наклон **0.006** ошибки
на шаг метки, после вычитания 0.18 / +0.49 / +0.18 — вердикты не меняются. `border`: наклон
**+0.708** (раскладка этого прохода короче, и позиция весит заметно больше), поправка двигает
контраст с 0.81 до 0.90 при p 0.0873 — вердикт «ниже порога различимости» не меняется ни до, ни
после поправки, хотя после поправки контраст подходит к границе значимости ближе, чем до неё.
⚠ **Поправка фазы Д (чек-лист приёмки №16 п.6): здесь стояло +0.646, а сырьё даёт +0.708.**
Величина, на которую делается поправка боевого контраста, печаталась не из тех голосов, из которых
считается сам контраст. Числа с поправкой (0.90, p 0.0873) при этом верны — они пере-сняты и
сходятся. Причина расхождения не установлена: промежуточные состояния разбора не сохранились, а
голоса с тех пор пере-снимались дважды (гонка §8 п.10 и пере-разбор репликации ниже). **В прибор —
сделано:** `verify23` сторожит наклон ЧИСЛОМ по каждому проходу; прежде он не трогал его вовсе,
хотя поправка на наклон — часть решающего правила.
**Агент-запусков: 58 при капе 60.** Первый (аннулированный) прогон 16 + пере-суживание 9 +
пере-прогон `tier` 12 + адверсариальное ревью 1 + пере-судейство `border` 12 + репликация 8 (§12а).
⚠ **Поправка фазы Д (чек-лист приёмки №16 п.4): в этой строке и в §14 п.9 стояло 50 — репликация
из счёта выпала.** Число 58 совпадает с §12а, где оно и было напечатано верно; расходились между
собой две строки одного отчёта.
⚠ Первые 38 сочтены МНОЙ, а не механизмом: `log_run` был написан и ни разу не вызван,
`agent-runs.json` не создавался, во всех голосах стоял `judge='?'`. С пере-судейства `border` учёт
персистируется — на диске 20 записей (12 боевых + 8 репликации), раскладка «кто какие единицы
судил» в `border-JUDGES.json` и `replication-runB-JUDGES.json`, в каждом голосе имя сессии.
**«Считает механизм» сказать нельзя** — файл записан РУКОЙ, скриптом, уже после судейства: в коде
`log_run` зовётся из одного места и единственную пометку `note='судейская сессия'` произвести может,
а три остальные, что стоят в записях, — нет. Значит: раскладка судей по единицам персистирована и
пере-снимаема, а **СЧЁТЧИК КАПА НЕ РАБОТАЕТ** — на диске 20 против 58 фактических, и он пропустил бы
ещё 60. Причина механическая: `log_run` вызывается из `--ingest`, то есть ПОСЛЕ суб-агента, а
сторожить кап можно только записью ДО запуска. Проход `tier` не покрыт вовсе: `tier-JUDGES.json`
не создан, во всех его голосах стоит `judge='?'`, и восстановить это задним числом нечем.
**Починено в фазе Д, не здесь:** `eval/dovodka/runs.py` пишет запись ДО запуска суб-агента, кап
проверяет перед записью и вдобавок ОТКАЗЫВАЕТ выдать те же токены незакрытой сессии — механический
замок против гонки §8 п.10. Селфтест 12/12.
**ПРОВЕНАНС КАТАЛОГОВ СУДЕЙСТВА `border` УТРАЧЕН (поправка фазы Д, п.8).** Боевые `aj-border` и
`aj-border-votes` — пере-штампованные копии `replication-runA-*`: содержимое побайтно совпадает
(`diff -rq` чист), а mtime всех файлов равен **2026-08-09 23:31:54**, то есть моменту копирования,
а не моменту судейства. Задания `aj-border-tasks` сохранили исходный mtime 17:55:26. Что из этого
следует: содержательно боевой прогон и есть `runA`, и все его числа пере-снимаемы; но доказать
ВРЕМЕНЕМ, когда именно судились боевые ответы, по диску больше нельзя — а гейт свежести разбора,
заведённый после гонки (§8 п.10), опирается именно на mtime. Для этого пака он теперь сравнивает
копии с копиями. Объявляю как ограничение, восстановлению не подлежит.
**ГОЛОСА РЕПЛИКАЦИИ НЕСЛИ ИМЕНА СУДЕЙ БОЕВОГО ПРОГОНА (поправка фазы Д, п.9).** `absjudge.ingest`
читает карту судей по имени ПРОХОДА (`blind-keys/border-JUDGES.json`), а репликация судила ТЕ ЖЕ 32
токена вторым жребием и лежала в своих каталогах — поэтому все 32 её голоса получили имена
`agent-02…agent-12` вместо собственных `agent-B1…B8`. Перевесы от этого не двигаются (имя судьи в
разность не входит, и §12а пере-снимается до знака: 0.62, ДИ [1.78, +0.53], p 0.3327, пол 1.00,
порог 1.77, декой 4.50 при 32/32), но разложение дисперсии по сессиям и любое «согласие судей»
считались бы по ложной раскладке. **Починено механизмом:** у рига появились крючки `DIRS_HOOK` и
`JUDGES_HOOK`, у прогона — свои каталоги и своя карта; `judge.py --run <прогон> <проход> --ingest`
пере-разобрал обе репликации. `replication-runA-JUDGES.json` при этом заведён копией боевой карты —
`runA` и есть боевой прогон, и теперь КАЖДЫЙ прогон несёт собственную карту, а не наследует её от
имени прохода.
## §7 ЧЕТЫРЕ ПОСЫЛКИ — СВЕРКА С ФАКТОМ
| # | посылка (записана ДО замера) | факт | итог |
|---|---|---|---|
| 1 | сильный тир НЕ обязан выигрывать; вероятный исход — не побьёт `deepseek-v4-pro` | ни один из троих не различим (0.19, +0.50, +0.19 при пороге 1.02) | **ПОДТВЕРЖДЕНА** |
| 2 | отказной режим размышления — свойство ВЕНДОРА, `kimi-k3` воспроизведёт отказ | пустой выход при 99.9% размышления — но клетка шла под СВОИМ потолком 4000 (§5) | **СОГЛАСУЕТСЯ, НЕ РАЗЛИЧАЕТ**: замер не разводит отказ вендора и обрыв на размышлении моим потолком |
| 3 | граница `glm-5` разрешима ростом n, а не сменой судьи | при n=32 и починенных контролях перевес сжался до 0.81, порога 1.48 не проходит (§3) | **ОПРОВЕРГНУТА как ожидание «разрешится», ПОДТВЕРЖДЕНА как прогноз «сожмётся»** |
| 4 | внешний контур нужен, но не заменяет мощность; отсутствие Sol не блокирует | пак закрыт без Sol; семейный слепой участок остался | **ПОДТВЕРЖДЕНА частично** |
⚠ Посылка 3 разошлась с прогнозом в другую сторону, чем объявлялось раньше. Пре-рег ожидал, что
при удвоении n перевес сожмётся; аннулированный прогон дал «устоял и стал значим», и это записали
как «ожидание не оправдалось». На исправленной базе перевес сжался — то есть прогноз пре-рега был
верен, а неверна была промежуточная запись. Сама посылка («разрешима ростом n») не подтвердилась:
удвоение n границу не разрешило, оно её закрыло в «не различимо».
**Выводы, которые эта сессия объявляла и которые оказались неверны.** Порядок важен, потому что
объявлений было три волны, и каждая была подписана как окончательная.
1. По числам ПЕРВОГО (аннулированного) прогона: «сильный тир бьёт боевой редактор», «вывод
эксп-22 перевёрнут», «граница разрешена в ноль», «посылка 1 опровергнута» — все четыре неверны,
держались на числах без контроля чувствительности судьи.
2. По числам ВТОРОГО прогона: «граница разрешена ПРОТИВ `glm-5`, результат эксп-22 реплицирован на
n=32» и закрытие Резерва D39.22 — сняты: половина материала шла с подменённой базой (§3).
3. В самом отчёте после этого ещё держались §13 («тир оказался лучше», «D39.22 закрыт В ПОЛЬЗУ
`glm-5`») — прямо против §1 и §3 того же документа, — и §12 с порогами из аннулированного
прогона. То есть отчёт какое-то время противоречил сам себе, и это тоже нашла приёмка.
Общий механизм у всех трёх волн один: связная таблица принималась за истинную, а смелость вывода
работала аргументом в его пользу. Ни одну из волн не остановил автор.
## §8 ДЕВИАЦИИ И ДЕФЕКТЫ — ОБЪЯВЛЯЮ
1. **Половина первого прохода отвергнута разбором** (8 из 16; во втором — 10 из 32). Причина одна:
судьи ставили ненулевой счёт по оси, не подпирая цитатой, а разбор выбрасывает такую единицу
ЦЕЛИКОМ. Это дефект МОЕЙ формулировки: в задании сказано, что обоснование обязательно, но не
сказано, что цена нарушения — потеря всей единицы. Пере-суживание с явной ценой снизило
отбраковку до нуля. Стоило 9 агент-запусков; денег не стоило.
2. **ЭХО-МИНА НА БОЕВОЙ БАЗЕ, И ГЕЙТ ПРОТИВ НЕЁ БЫЛ НАПИСАН, НО НЕ ПОДКЛЮЧЁН.**
`et-unit-draft-deepseek-v4-flash-63ab55ac5c`: 2243 знака при исходнике 2182, `finish=stop`
и в этих 2243 знаках **1798 иероглифов при НУЛЕ кириллицы**. Это не «пересказ вместо перевода»,
как утверждала прежняя редакция пункта, а классическая эхо-мина: модель вернула исходник.
Прежняя редакция объявляла и то, что «ни один гейт пака этого не ловит» — тоже неверно:
`bakeoff.draft_reject_reason` ловит её штатно (пере-снято: вердикт «эхо исходника»), просто
фаза B звала только проверку «строка не пуста». Гейт подключён; на этом паке он забраковал бы
ровно эту единицу и больше ни одной из шестнадцати.
**Чувствительность вывода к ней замерена:** без неё контраст границы 0.74 (p 0.21) против
0.81 (p 0.16) на всех 32 — порог 1.48 не проходит ни так, ни так, вердикт §3 не меняется.
Что здесь верно и что было сформулировано неточно: вход у обоих армов контраста ДЕЙСТВИТЕЛЬНО
одинаковый — это одна и та же база, — но оба редактора выдали полную длину, то есть достроили
пропущенное из исходника и решали на этой единице ДРУГУЮ задачу (перевод, а не редактуру).
Единица оставлена и объявлена. **В прибор: гейт «длина клетки против длины ИСХОДНИКА с
поправкой на пару языков»; сравнение с медианой соседних клеток этот случай не ловит, а на
разноязычных пулах даёт ложные срабатывания (см. снятый пункт ниже).**
3. **СНЯТО — было ложным срабатыванием, и оно уже ушло оркестратору.** Прежняя редакция объявляла,
что в сырье эксп-22 три клетки боевого редактора (`8e50448cea`, `b065a92dc0`, `53f1a12dff`)
оборваны до 22% длины, и отдавала это оркестратору как незамеченный дефект чужого пака.
Пере-проверка: все три — из АНГЛИЙСКОГО пула (`manifest.json`, ключ `en`), их исходники ~1630
знаков, выходы 1590/1596/1638 при `finish=stop`, то есть ≈100% исходника. «22%» получалось
только от сравнения с медианой пула, где преобладают zh-единицы с трёхкратным расширением.
По собственному критерию этого отчёта («<70% медианы СВОЕЙ единицы») таких клеток ноль.
Оркестратору: пункт отозван, дефекта эксп-22 здесь нет. ⚠ Прежняя редакция писала, что ложный
пункт «уже ушёл оркестратору» — носителя у этого не было: отчёт не отслеживался git, в журнале
и D-логе исходного утверждения нет. Носитель появился только сейчас, вместе с отзывом, — запись
в `docs/PROGRESS.md` секции «Полигон» и §15 отчёта эксп-22.
4. **Девиация протокола судейства.** Две сессии сообщили, что СРАВНИВАЛИ варианты между собой
(«чтобы точнее процитировать различия»), хотя задание требует оценивать каждый вариант только
против исходника. Смещение от такого сравнения бьёт по арму-одиночке, а в проходе `tier`
одиночка — как раз боевой редактор. Величину смещения по двум сессиям не оценить; факт
объявлен. **В прибор: запрещать не только сравнение, но и чтение вариантов рядом.**
5. **Двусмысленная инструкция про оборванный вариант — относилась к клетке, которой в принятой
конфигурации нет.** Я велел судье «оценивать как есть и не достраивать»; судья прочитал это как
«не штрафовать за обрыв». Клетка, на которой это наблюдалось, попадала в пачку только через
дефект базы R0 (§0) и в исправленном ключе отсутствует; в другом голосе того же прогона судья
пропуск, наоборот, засчитал. Формулировка всё равно неверна и правится («оценивать как есть» и
«не штрафовать за неполноту» — разные вещи), но как НАБЛЮДЕНИЕ пункт снят: воспроизводимого
случая в принятых данных нет.
6. **Реализация скрина применила критерий, которого нет в пре-реге.** Код переиспользовал вердикт
эксп-22 целиком, вместе с ПЕРЕВОДЧЕСКИМ порогом $0.02, — а пак скринит редакторов, и пре-рег
называет только редакторский порог $0.06. Лишний порог снял `gpt-5.6-terra` и `grok-4.5`, то
есть обоих, ради кого пак существует. Снимать критерий после результата — подгонка, поэтому
печатаются ОБА вердикта: пре-рег-критерий как несущий, полный критерий эксп-22 как справка
(§10). Провенанс: фризы `c0dd228` 02:44:28 и `87247e2` 02:45:04, первая покупка **02:45:35**
запас 31 секунда, а не минута, как читалось. ⚠ «План старше обоих» с диска НЕ доказуемо: план
пака лежит в эфемерном scratchpad и в git не фризовался (`git log --all -- '*plan23*'` пуст),
колонка улик его реестра дописывалась по ходу. Неизменяемый провенанс здесь несут только
фриз-коммиты и запись сессии — то же снижение, что уже сделано в §6.
7. **Я СНЯЛ ЗАМКИ У ЖИВОГО ПОКУПАЮЩЕГО ПРОГОНА.** Увидев в каталоге сырья зависшие `*.lock` и
решив, что они остались от упавшего процесса, я удалил их, пока покупки ещё шли. Атомарный
замок (`money.py:219`, `O_CREAT|O_EXCL`) — единственное, что разводит параллельных покупателей;
именно его отсутствие стоило эксп-22 верхней границы невозвратной пере-оплаты ≤$0.846103 на 176
клетках (его §-о деньгах). Ущерба не случилось только потому, что второго покупателя в тот
момент не существовало: удача, а не дисциплина. Ни один гейт этого не ловит и поймать не может —
удаление файла руками вне кассы. **В прибор:** снимать замок только тем же процессом, что его
поставил, а «зависший» замок опознавать по живости PID, а не по виду каталога.
8. **Коллизия само-импорта, четырежды.** Модули пака (`roster`, `screen`, `money`, плюс тестовый
файл) грузились по имени и подхватывали одноимённые модули эксп-22 вместо своих. Один случай
прошёл МОЛЧА и исказил замер: селф-тест проверял 11 моделей из 15 и печатал зелёное. Чинено
явной загрузкой по пути (`_load`), но найдено это чтением вывода, а не гейтом.
9. **Фриз фазы A попутно изменил риг ЧУЖОГО пака.** Тот же коммит `c0dd228` тронул
`eval/tenant_panel/material.py` (приколка единиц манифестом) и `money.py` под заголовком про
editor-tier. Зона не нарушена (весь `eval/` — зона полигона) и изменение объявлено в §11, но
код, породивший уже принятые числа эксп-22, изменён задним числом под чужим заголовком.
Гейты эксп-22 после этого пере-снимались; их зелёность после правки — в §14, вопрос 7.
10. **ГОНКА ДВУХ КРУГОВ СУДЕЙСТВА И РАЗБОР В ЕЁ СЕРЕДИНЕ.** Пять единиц прохода `border`
(`13514e13f0`, `15f002335d`, `1ffe99dc43`, `225001778a`, `87dd50b129`) я отдал на пере-суживание
ДВАЖДЫ, не дождавшись первого круга: второй круг записал ответы, я прогнал `--ingest` в 18:36:56,
а первый круг дописал те же самые файлы в 18:3818:41. В итоге голоса были от одного круга, а
сырьё на диске — от другого, и расходились они по всем пяти единицам. Числа при этом сходились
с отчётом, потому что и отчёт, и гейт читали ГОЛОСА: ни `verify23`, ни `itog23` не сравнивали их
со временем ответов. Нашёл аудит закрытия заказа, не автор и не первая приёмка.
**Как разрешено.** Голоса пере-снят с текущего сырья — правило «сырьё на диске главнее», а не
«то, что уже напечатано»: иначе опубликованное число было бы невоспроизводимо для читателя.
Оба набора я видел ДО выбора правила, и это надо знать при чтении: первый круг давал 0.59
(порог 1.43), второй 0.81 (порог 1.48). Вердикт «ниже порога различимости» одинаков в обоих, а
вот расщепление половин при этом заметно двигается (было 1.44/+0.25, стало 1.62/0.00) — то
есть устойчив здесь только знак и вывод, но не величины по подвыборкам.
**В прибор — сделано:** `verify23` роняет пак, если хоть один ответ судьи новее своего голоса.
Это первый гейт пака, сторожащий не число, а ПОРЯДОК СОБЫТИЙ.
11. **Моя ошибка чтения по ходу.** На n=14 я озвучил «преимущество не различимо», зная, что две
единицы ещё не вернулись. Вывод на неполных данных — та же ошибка, что и вывод на агрегате без
вскрытия единиц.
**Поправка фазы Д (чек-лист приёмки №16 п.10): нумерация этой секции была сломана.** На диске
шёл ряд 16, 8, 9, 10, 7, 7 — два разных дефекта под номером «7», и ссылка §14 п.7 вела на «§8 п.8»,
которого не существовало. Пункты пере-нумерованы по порядку в документе (тексты не тронуты), ссылки
на них поправлены. Причина сбоя видна по содержанию: пункты 7 и 11 дописаны последними, аудитом
закрытия заказа, и вставлены в хвост без сверки с уже занятыми номерами.
## §9 ЧТО НЕ ПЕРЕНОСИМО
* **Проход `tier` по-прежнему снят ОДНОЙ модельной семьёй.** Для `border` ограничение снято
внешним судьёй (§12в); тексты `tier` он не читал, и вывод «сильный тир не отличим» подписи вне
Claude не имеет.
* **ОДНОЙ МОДЕЛЬНОЙ СЕМЬЁЙ СНЯТЫ НЕ ТОЛЬКО ЧИСЛА, НО И ВСЯ ПРИЁМКА.** Это признаётся здесь впервые
и это отдельный дефект, а не повтор предыдущего пункта. D39.120 требует на текстовых и
оценочных линзах хотя бы одного опровергателя ДРУГОГО модельного семейства; владелец сверх того
прямо разрешил сессии запустить агентов Fable-5 под ревью. Ни то, ни другое не исполнено: все
судьи, все линии приёмки, все опровергатели и весь аудит закрытия заказа отработали на Opus-5.
То есть монокультура, которую отчёт объявляет ограничением ЗАМЕРА, ровно в той же мере поражает
и МЕХАНИЗМ ПРОВЕРКИ замера — а именно он трижды подписывал числа, оказавшиеся артефактами.
Разрешение владельца на другое семейство лежало неиспользованным всё время работы пака.
* **Внешний контур есть у `border` и НЕТ у `tier`** — ⚠ поправка фазы Д (чек-лист приёмки №16 п.5).
Прежняя редакция этого буллета гласила «все судейские числа сняты одной модельной семьёй,
внешнего судейского контура нет и здесь», то есть прямо против §12в того же отчёта, где подпись
судьи вне Claude по проходу `border` напечатана числами (1.00 при пороге 3.53, декой 32/32).
Верная форма: для `border` ограничение СНЯТО, для `tier` — стоит в полную силу (первый буллет
этой секции). Отдельная линия — Sol-пакет (а) эксп-22: прогнан владельцем 09.08, но арбитраж не
состоялся (эксп-22 §16: три единицы на контраст, дрейф шкалы ×2.6, знак совпал в 3 парах из 9);
пакет (б) не запускался. Ограничение по `tier` несущее, а не формальное: его вывод — что
перевесы сильного тира НЕ доходят до порога, — есть утверждение о том, чего судья не различил,
и оно тем слабее, чем однороднее судьи. Отдельно: идентичность судей `tier` не персистирована
(§6), поэтому согласие между ними даже внутри семьи посчитать нечем.
* **Декой во всех единицах принятого прохода `tier` посажен из одного и того же арма — R0**
(замер по ключу: 16/16), а R0 стоит вторым армом в каждом контрасте этого прохода. Если само
присутствие испорченного близнеца двигает оценку донора, оно двигает её в одну сторону во всех
трёх контрастах сразу. Прямой улики за или против нет: ни один сохранённый голос не содержит
рассуждения «это копия другой метки». Косвенная — R0 против голого черновика даёт +2.06, а
T1/T2/T3 против него же +1.88 / +2.56 / +2.25, то есть R0 лежит ВНУТРИ разброса панели, грубого
смещения донора не видно. Для прохода `border` донор с этого пере-прогона уравнен: 16 единиц
из R0, 16 из R2.
* **Одна книга, одна пара языков, 16 единиц панели.** Всё, что зависит от свойств текста, обязано
пере-меряться.
* **Вывод про `gemini-3.1-pro` по-прежнему отсутствует** — он не входил в этот пак.
* **`kimi-k3` замерен ОДНОЙ клеткой** по объявленному особому режиму И под своим потолком вывода
(§5): твёрдым остаётся только вердикт по ЦЕНЕ; ни о качестве его прозы, ни об отказном режиме
вендора этот замер не говорит.
## §10 ФАЗА A — СКРИН СИЛЬНОГО ТИРА
Пороги взяты у эксп-22 БЕЗ изменения — порог из прошлого пака единственное, что защищает состав
от подгонки, а состав здесь и есть предмет спора.
```
модель вердикт ед. $/ед edit размышл причины
gpt-5.6-terra прошёл 4 0.04250 6.6%
grok-4.5 прошёл 4 0.05481 69.5%
glm-5.2 прошёл 4 0.01792 0.0%
kimi-k3 ПРОВАЛ 1 0.08041 99.9% Г5 цена editor > $0.06 · Г6 выход пуст
```
**Справка — полный критерий эксп-22** (с переводческим порогом $0.02, которого в пре-реге этого
пака нет): `gpt-5.6-terra` ПРОВАЛ ($0.03668), `grok-4.5` ПРОВАЛ ($0.03445), `glm-5.2` прошёл
($0.01396), `kimi-k3` ПРОВАЛ. Разница между двумя колонками — целиком §8 п.6.
Цены — с ВЕНДОР-страниц 09.08: `gpt-5.6-terra` $2.00/$0.20/$12.00 · `kimi-k3` $3.00/$0.30/$15.00 ·
`grok-4.5` $2.00/$0.30/$6.00 · `glm-5.2` $1.40/$0.26/$4.40. Слаги — живой листинг `/models` того же
дня. Попутно закрыта дыра эксп-22: его цена `gpt-5.6-luna` не подтверждалась живьём (Cloudflare
403) и ехала из прошлых паков — страница открылась и подтвердила её ровно.
## §11 МАТЕРИАЛ
16 НОВЫХ единиц из 16 разных глав (22, 2530, 3236, 3942), знаков 2077…2251, медиана 2141,
макс. попарная близость 0.091. Пересечений с эксп-22 — ноль: его главы исключены ЦЕЛИКОМ, иначе
«новые» единицы были бы соседними абзацами тех же глав и делили бы с ними сцену и лексику, а
прирост мощности оказался бы мнимым. Гейт прав пересчитан по СВОЕМУ пулу (верхний дециль по
плотности эротических маркеров), а не перенесён константой «2» из эксп-22.
**Побочный эффект докачки глав, который пришлось чинить.** Материал эксп-22 отбирался «из
середины распределения длин» по содержимому диска; докачка с 18 глав до 42 СДВИНУЛА его единицы
(главы 318 → 2,5,9…41), то есть его гейты стали бы считать по материалу, который никто не
покупал. Единицы эксп-22 приколоты к его манифесту отбора; все четыре его гейта после этого
зелёные. Правило отбора теперь применяется только когда манифеста ещё нет.
## §12 КОНТРОЛИ РИГА (читать ДО боевых чисел)
```
проход ДЕКОЙ (порча против чистого) ПОЛ (истинная разница ноль) БЛИЗНЕЦ
tier 3.94 поймано 16/16 ✔ 0.62 [1.38, +0.06] порог 1.02 пара CTRLfloorA≡T1, ноль
border 4.88 поймано 32/32 ✔ 1.00 [2.06, +0.06] порог 1.48 побайтных дублей нет
```
**Декой** — посаженная в чистый текст деградация — обязателен в каждом проходе: судейская сессия,
не поймавшая его отрицательным знаком, аннулируется целиком (так эксп-22 списал 6 агент-запусков).
Пол ЧЕСТЕН: ноль внутри интервала, и порог различимости берётся ИЗ НЕГО — это решающее правило
пака, поэтому §1 читается против 1.02, а §3 — против 1.48.
⚠ **Прежняя редакция этой секции печатала пороги 1.65 и 1.58 — они из ПЕРВОГО, аннулированного
прогона** (пере-снято: `annulled-run1` даёт ровно 1.65 и 1.58). Секция объявлена «читать ДО боевых
чисел» и задаёт порог различимости, так что отчёт выдавал читателю два разных порога на одну
величину: §1 читался против 1.02, а §12 печатал 1.65 — и при напечатанном 1.58 тогдашний вывод §3
в порог не проходил вовсе, то есть документ противоречил сам себе по несущему вердикту.
Средние 0.29 и 0.42 из той же строки не
воспроизводятся НИ ИЗ ОДНОГО набора голосов на диске и при n=16 недостижимы арифметически (среднее
целых ошибок кратно 1/16); откуда они взялись — не установлено, промежуточные состояния разбора не
сохранились. Гейт этого не ловил: он сверял лишь наличие слова «пол». Починено — `verify23.py`
теперь сторожит и величину пола, и порог, и величину декоя числом.
**«БЛИЗНЕЦ» в проходе `tier` — не контроль.** Единственная побайтно совпадающая пара пачки это
`CTRLfloorA` и `T1`, в 16 единицах из 16, и совпадают они по построению: первой половиной шумового
пола `panel.floor_pair` отдаёт саму боевую клетку `T1`, докупается только вторая генерация.
«Перевес ровно ноль в 16/16» означает лишь, что один и тот же текст под двумя метками получил одну
оценку внутри одного ответа судьи; воспроизводимость оценки из этого НЕ следует, и как контроль
эта строка пуста. Замерено и смежное: связи между оценкой пары и боевым перевесом нет (корр.
0.05). Против такого переиспользования прямо предупреждает комментарий в риге эксп-22 — я его
воспроизвёл. **В прибор: половиной пола брать отдельную генерацию, не клетку панели.**
## §12б ПОРОГ БЫЛ ЗАНИЖЕН НА 19% — РАЗЛОЖЕНИЕ ДИСПЕРСИИ
Найдено после сдачи, вопросом владельца «ты не преуменьшаешь?». Шумовой пол этого рига меряет
пару, у которой ОБЕ половины судит один и тот же судья, — значит межсудейская разница в него не
попадает по построению, а боевые перевесы её несут. Порог, взятый из такого пола, занижен.
Разложение дисперсии перевеса по 8 сессиям прохода `border` (n=32):
```
внутри сессии sd 2.903
между сессиями sd 1.030 ← в шумовой пол НЕ попадает
se среднего 0.513 наивная → 0.629 с учётом межсессионной (×1.23)
ПОРОГ 1.48 напечатанный → 1.76 честный
```
Пересчёт вердиктов по честному порогу — **ни один не двигается**, и все, кроме одного, крепнут:
```
R2 glm-5 vs R0 0.81 · 1.48 → 1.76 · ниже порога
T1 glm-5.2 0.19 · 1.02 → 1.53 · ниже порога
T2 gpt-5.6-terra +0.50 · 1.02 → 1.68 · ниже порога
T3 grok-4.5 +0.19 · 1.02 → 1.55 · ниже порога
R0 vs F КОНТРОЛЬ +2.06 · 1.02 → 1.94 · РАЗЛИЧИМ
```
**Почему занижение порога не породило ложных выводов.** Заниженный порог делает вывод «различимо»
слишком лёгким. Все несущие выводы пака отрицательные — заниженный порог им не помогает, а мешает;
единственное положительное утверждение это позитивный контроль, и он проходит с запасом даже по
строгому порогу. Направление риска здесь — что пак ПРОЗЕВАЛ реальную разницу, а не выдумал её.
**Для прохода `tier` межсессионная компонента ЗАИМСТВОВАНА с `border`.** Оценить её на своих
данных нельзя: идентичность судей `tier` не персистирована, во всех голосах стоит `judge='?'`
прямое следствие дефекта учёта (§6). Задним числом не восстановить.
**Эмпирическая проверка этой поправки — §12а.** Прямая репликация новым жребием судей дала
расхождение средних 0.19 при ожидании ≈1.0, то есть поправка скорее консервативна. Уточнять её
по двум прогонам нельзя — это оценка по n=2.
**В прибор:** шумовой пол обязан браться парой, чьи половины судят РАЗНЫЕ сессии. Тогда он ловит
обе компоненты, и порог не требует ручной поправки.
## §12в ВНЕШНИЙ СУДЬЯ ВНЕ СЕМЬИ Claude — ПРОХОД `border` ЗАКРЫТ ТРЕТЬЕЙ ПОДПИСЬЮ
Заказано владельцем после того, как он указал: все выводы пака сняты агентами одной модельной
семьи, и мнения судьи другой семьи о КАЧЕСТВЕ этих переводов никто не спрашивал. Замечание было
верным — предыдущая попытка внешнего контура (Sol-пакет эксп-22) судила тексты ЧУЖОГО пака и
развалилась по устройству (эксп-22 §16).
**Что сделано.** Внешнему судье (не-Claude, локально, руками владельца, 4 сессии по 8 единиц)
отданы ТЕ ЖЕ файлы заданий `aj-border-tasks/*.txt`, которые судили агенты пака: тот же исходник,
те же армы под теми же слепыми метками, тот же декой и тот же шумовой пол в пачке. Поэтому его счёт
сопоставим с моим текст-в-текст. Пере-снимается командой
`eval/.venv/bin/python eval/editor_tier/solscore.py`; ответы в `~/books/editor-tier/sol-border/`.
```
судья уровень перевес R2 пол порог вердикт
внешний, не-Claude 11.0 1.00 1.88 3.53 ниже порога различимости
мой прогон A 7.0 0.81 1.00 1.48 ниже порога различимости
мой прогон B 7.0 0.62 1.00 1.77 ниже порога различимости
```
Принято 32 из 32 единиц, отказов ноль, ДЕКОЙ пойман **32/32**.
⇒ **Ограничение «все судейские числа сняты ОДНОЙ модельной семьёй» для прохода `border` снято —
но снято СЛАБО, и это надо читать вместе с числами.** Вывод «граница `glm-5` не разрешена, эффект
ниже порога различимости» подпись судьи вне Claude имеет. ⚠ Поправка фазы Д, найденная приёмкой
другого модельного семейства: **у внешнего прибора шумовой пол sd 5.05 и порог 3.53**, а боевой
контраст прохода 0.81. Значит внешний судья не мог противоречить нулевому вердикту НИ ПРИ КАКОМ
исходе, кроме эффекта вчетверо больше замеренного — он и позитивный контроль масштаба +2 не
развёл бы. Подпись под отрицательным выводом на таком приборе почти автоматична, и «ограничение
снято» звучит сильнее, чем даёт замер. Честная форма: **внешний контур подтвердил, что эффекта
РАЗМЕРА, ВИДИМОГО ЕМУ, здесь нет; про эффекты меньше 3.5 ошибок он не высказывался.**
⇒ И следствие вперёд: та же оговорка накроет внешнюю подпись прохода `tier` (заказ фазы Д, п.Д2) —
там боевые перевесы 0.19…0.50, то есть на порядок ниже разрешения внешнего прибора. Объявляется
ЗАРАНЕЕ, до прогона, а не после: подпись будет означать «внешний судья тоже не увидел», а не
«внешний судья подтвердил равенство».
Для прохода `tier` внешней подписи по-прежнему нет: судья его текстов не читал.
**Замер межсемейного расхождения — побочная находка, для рига ценнее самого вердикта.**
```
согласие по единицам (корреляция перевесов) знак совпал
внешний ↔ мой A +0.317 22/32
внешний ↔ мой B +0.334 24/32
мой A ↔ мой B +0.627 26/32
```
Расхождение МЕЖДУ семьями примерно вдвое больше, чем ВНУТРИ семьи. И прибор другой семьи заметно
грубее: его шумовой пол sd 5.05 против 2.12 у моих, отсюда порог 3.53 против 1.48 — он честен, но
разрешает вдвое хуже. Практическое следствие: **для вопроса «есть ли разница вообще» судьи разных
семей взаимозаменяемы; для контраста тоньше двух ошибок на единицу смешивать их в один пул без
нормировки нельзя** — различаются и шкала, и разрешение.
## §12а РЕПЛИКАЦИЯ ПРОХОДА `border` НОВЫМ ЖРЕБИЕМ СУДЕЙ
Заказана владельцем как проверка методики: «давай перегоним и посмотрим». Меняется РОВНО один
фактор — состав судейских сессий. Ключ, слепые метки, посадка декоя, тексты и задания те же файлы,
не пере-выпускались. Правило публикации записано ДО запуска: прогон A остаётся опубликованным,
B — проверка; при расхождении вердиктов находкой считается само расхождение, а не удобный прогон;
складывать A и B в один набор запрещено — это разные жребии.
```
прогон A прогон B
перевес R2 vs R0 0.81 0.62
95% ДИ [1.81,+0.28] [1.78,+0.53]
p (Холм) 0.1601 0.3339
шумовой пол 1.00 1.00
порог прохода 1.48 1.77
декой 4.88, 32/32 4.50, 32/32
уровень (медиана) 7.5 6.8
```
**Вердикт «ниже порога различимости» воспроизвёлся.** Расхождение средних |AB| = **0.19** при
пред-объявленном ожидании ≈1.0 — то есть прибор на уровне ВЫВОДА заметно устойчивее, чем следовало
из моей же оценки межсудейской компоненты. Корреляция перевесов по единицам +0.63, знак совпал на
26 единицах из 32.
**Что при этом НЕ воспроизводится — отдельные единицы.** Разность перевесов по-единично имеет
sd 2.78 при среднем +0.19: на ОТДЕЛЬНОЙ главе армы не упорядочены, новый жребий судей переставит
их местами примерно в каждой пятой. Устойчиво только среднее по единицам. Риг об этом
предупреждает в собственном выводе, теперь это подпёрто прямым замером, а не рассуждением.
**Цена:** 8 агент-сессий, по паку стало 58 из капа 60. Покупок не потребовалось. Оба прогона лежат
рядом: `~/books/editor-tier/replication-runA-*` (опубликованный) и `replication-runB-*`.
## §13 ДИСПОЗИЦИИ
⚠ Прежняя редакция этой секции несла выводы АННУЛИРОВАННОГО прогона («тир оказался лучше»,
«D39.22 закрыт в пользу `glm-5`») — прямо против §1 и §3 того же отчёта, который сам перечисляет
эти выводы как снятые. Секция, из которой переносится закрытие строк реестра, была последней, где
я не сверился с собственными числами.
* **Строка про сильный тир (дыра эксп-22 §13а)****ЗАКРЫТА**: тир проверен, различимого выигрыша
над боевым редактором нет, а стоит он в 26 раз дороже (§1, §4). Платить не за что; рекомендация
эксп-22 (`deepseek-v4-pro`) остаётся и теперь стоит на панели, включающей сильный тир OpenAI,
xAI и Z.AI. Решения владельца по цене НЕ требует — требовало бы при обратном знаке.
* **Резерв D39.22 (`glm-5`)****ЗАКРЫТИЕ НЕ ОБЪЯВЛЯЮ, подаю ПРЕДЛОЖЕНИЕ.** Это ратифицированное
решение владельца, и права закрывать его полигон-сессии не давалось; эксп-22 от закрытия
воздержался. Замер: на 32 единицах с полным набором контролей `glm-5` от боевого редактора **не
отличим** (0.81 при пороге 1.48, §3), то есть основания «он значимо хуже», на которое сессия
дважды ссылалась, НЕТ. Предложение: снять резерв как контраст, требующий отдельного разбора, и
вести `glm-5` по общему правилу ничьей D39.117 — а оно оставляет `deepseek-v4-pro`, поскольку
тот дешевле. Отдельно к решению: ToS-гейт Z.AI на прод-выбор `glm-5` никуда не делся (§9 эксп-22).
* **Sol (строка 150)** — пакет (а) прогнан владельцем 09.08 и дал отрицательный результат по своей
задаче (эксп-22 §16); пакет (б) НЕ запускать до починки оснастки: та же конструкция, `N_UNITS_B = 3`.
**Поправка фазы Д (п.5):** здесь стояло «внешнего судейского контура у обоих паков по-прежнему
нет» — неверно и против §12в. Контур ПОСТРОЕН и сработал, но на заданиях абсолютного рига, а не
на пакетах конструкции Sol, и покрывает он проход `border`. Не покрыты: проход `tier` (задания
`aj-tier-tasks` внешнему судье не отдавались) и оба контраста эксп-22, ради которых пакеты (а)/(б)
и делались.
* **Строка 153 и вторая база Ф3** — не гнались, как и в эксп-22.
## §14 CONFIRM / DENY — К ПОДПИСИ ВЛАДЕЛЬЦА
Ниже — всё, что этот пак принял на веру, назвал решением или сделал вне объявленного. Ни один
пункт не считается ратифицированным, пока не подтверждён; пометка «со слов сессии» означает, что
артефакта в репозитории нет и проверить нечем, кроме записи разговора.
| # | что | статус |
|---|---|---|
| 1 | **Мандата на пак 23 не существует** — промта нет, пак самоназначен, санкционированы только деньги | подтвердить задним числом или отменить |
| 2 | Потолок пака **$4.00** и запас **$5** — со слов сессии, в репо артефакта нет | подтвердить провенанс |
| 3 | **Резерв D39.22** закрывать не мне: это решение владельца (§13) | принять к сведению |
| 4 | Правило «судейская сессия без пойманного декоя аннулируется целиком» живёт только в промте ЧУЖОГО пака; я применил его к себе дважды | ратифицировать как норму или отменить |
| 5 | Потолок ФАЗЫ поднимался дважды в ходе пака, резерв ушёл не на объявленное назначение, пинга не было (§6); пакетный потолок не пробит | норма или запрет |
| 6 | Учёт агент-запусков до этого пере-прогона вёлся мной, а не механизмом (§6); с пере-судейства `border` он персистируется, но задним числом первые 38 восстановить нечем | принять со слов или считать неучтённым |
| 7 | Фриз фазы A пака 23 попутно изменил риг эксп-22 (§8 п.9). Его гейты пере-сняты сейчас — `verify22.py` и `itog22.py` зелёные, код возврата 0 | к сведению; вопрос только в том, нормально ли править чужой пак под своим заголовком |
| 8 | Провенанс цен OpenAI и xAI — снимка вендор-страницы нет, только живой листинг `/models` и комментарий в коде | принять или требовать снимок |
| 9 | Пере-судейство прохода `border` — 12 агент-сессий, денег $0; всего по паку **58 сессий** при капе 60. ⚠ Поправка фазы Д (п.4): здесь стояло «50» и «теперь считает механизм» — первое не считало репликацию (§12а), второе прямо опровергнуто §6: на диске 20 записей против 58 фактических, СЧЁТЧИК КАПА НЕ РАБОТАЕТ. Персистирована только раскладка судей по единицам | санкция задним числом |
---
# ФАЗА Д — ДОВОДКА ЭКСП-22/23 (заказ владельца 10.08)
Исполнение `docs/POLYGON_EXP2223_REDO_SESSION_PROMPT.md`. Это НЕ новый эксперимент: выводы паков
22 и 23 признаны владельцем непоказательными **по постановке** и заморожены до этой фазы. Зона —
`eval/dovodka/` + правки рига паков 22/23 + эти секции.
## Д0 ПРЕ-РЕГ (зафризен своим коммитом ДО первой покупки)
### Д0.1 Гипотезы владельца — ДОСЛОВНО из заказа
> **H-1 «Проблема в черновике»**: flash даёт полумусор, полный перепис нужен именно поэтому;
> связка «качественный черновик + ТОЧЕЧНЫЙ редактор» ≥ боевой связки по качеству при сравнимой цене.
>
> **H-2а «Флаговый edit-контур»**: детерминированные флаги (батарея + канон-гейт) → точечный
> фиксер — не хуже full-regen по судье, дешевле по деньгам, НЕ ломает канон.
>
> **H-2б «Смысловой edit-контур» (главная ставка владельца, 10.08)**: LLM-критик, читающий и
> ПОНИМАЮЩИЙ текст («найди всё проблемное») → точечный фиксер — дороже (поиск ≈2.14× переписа),
> но качество выше и full-regen, и флагового контура: «флагами всё не отследишь». Цена заложена
> в смету сознательно — гипотеза о КАЧЕСТВЕ, не об экономии.
>
> **H-3 «en: перепис не нужен»**: на en→ru редактору остаются только синк глоссария и точечные правки.
>
> **H-4 «dspro-китайскость»**: перевес dspro в редакторской роли — свойство пары zh→ru; на en/ja
> порядок жильцов может смениться (dspro — китайская модель, книга — китайская).
### Д0.2 ⛔ ДВА АРМА ЗАВЕДЕНЫ ПРИЁМКОЙ, А НЕ АВТОРОМ — и без них две гипотезы не мерились
Первая редакция плана этой фазы **не отвечала на H-1 и H-4**, и нашла это не сессия, а приёмка
другого модельного семейства (Fable-5, разрешение владельца D39.120 наконец использовано):
* **H-1** говорит о связке «КАЧЕСТВЕННЫЙ черновик + точечный редактор». Ни один арм плана такой
связки не содержал: `A1`/`A3` ставят фиксер на flash-черновик — то есть на полумусор по самой
формулировке гипотезы, — а `A2` есть однопроходка вовсе без редактора. Гипотеза «отвечалась» бы
интерполяцией между армами, где взаимодействие «фиксер × качество базы» не меряется вовсе.
⇒ заведён арм **A6 = dspro-черновик эксп-22 + оба контура**. Черновики уже куплены ($0 за базу).
* **H-4** говорит о смене ПОРЯДКА жильцов на другой паре. Порядок проверяется только панелью из
НЕСКОЛЬКИХ редакторов, а на en-оси редактор стоял один — `deepseek-v4-pro`. Фаза сказала бы
«сколько покупает редактура на каждой паре» (это H-3), но не «остаётся ли dspro лучшим».
⇒ заведён арм **E6 = второй редактор `glm-5` на en поверх той же базы**.
Стоимость починки $0.48; потолок фазы поднят владельцем $4.00 → **$4.50** ровно под неё.
### Д0.3 Мощность каждой оси — ДО покупок (`eval/dovodka/power.py`)
```
ось n k sd MDE ЦЕЛЬ потолок p СТАТУС
Д4 edit-контур zh 32 5 2.12 1.29 1.50 0.0000 НЕСУЩАЯ
Д3 en→ru несущая 16 5 2.12 1.83 2.00 0.0002 НЕСУЩАЯ
Д6 ja→ru разведка 9 3 2.90 3.33 2.00 0.0117 ⛔ ОПИСАТЕЛЬНАЯ
Д1 gemini добивка 16 1 2.12 1.83 2.00 0.0000 НЕСУЩАЯ
```
**ЦЕЛЬ — искомый эффект, объявленный ДО денег.** Без неё MDE не решает ничего: печатать «MDE 1.83»
и не сказать, меньше он искомого или больше, — ровно та форма, за которую погорела en-ось эксп-22
(там при n=6 и k=6 потолок p был 0.1875, то есть значимость недостижима ПО ПОСТРОЕНИЮ, и выяснилось
это после денег). Ось объявляется описательной, если MDE больше цели ЛИБО значимость недостижима.
**Следствие, которое надо знать заранее:** при n=12 (минимум промта) ось Д1 имела бы MDE 2.11
против цели 2.00 и была бы ОПИСАТЕЛЬНОЙ. Несущей её делает решение владельца взять полные 16.
**Прайор шума — не свой пол.** Своего пола у будущего прохода нет по построению; берётся худший
из замеренных на той же структуре армов (`border`, sd 2.12). **Решает СВОЙ пол**, и если он выйдет
хуже прайора, ось пере-классифицируется в описательную ТОГДА ЖЕ — до чтения боевых перевесов.
### Д0.4 ⛔ ЗАПАС ЭКВИВАЛЕНТНОСТИ для H-2а — иначе «не хуже» доказать нечем
H-2а утверждает, что флаговый контур **НЕ ХУЖЕ** полного переписа. Риг устроен так, что
«ниже порога различимости» **не означает «равны»** — это записано во всех отчётах и здесь не
меняется. Значит подтвердить H-2а обычным контрастом невозможно в принципе: отсутствие значимой
разницы не есть равенство, и CONFIRM был бы сделан формулировкой, а не числом.
**Пред-объявляю правило non-inferiority:** `A1``A3`) признаётся «не хуже» `A0`, если **нижняя
граница 95% ДИ контраста выше 1.50** — той же величины, что объявлена целью оси Д4 (треть
собственного эффекта редактора +4.44 в эксп-22 Ф3). Три исхода и все три названы заранее:
* нижняя граница > 1.50 **и** верхняя < +1.50 → **НЕ ХУЖЕ** (H-2а подтверждена по качеству);
* верхняя граница < 1.50 → **ХУЖЕ** (H-2а опровергнута);
* интервал накрывает 1.50 → **НЕ УСТАНОВЛЕНО** при данном n, и это честный третий исход, а не
провал: мощность объявлена, доборы обсуждаются с владельцем.
### Д0.5 Оси, армы и семейства контрастов (впечатываются в ключ ДО первого ответа)
**Д4 — edit-контур zh, 32 единицы (16 эксп-22 + 16 эксп-23), база одна и замороженная:**
```
A0 боевой full-regen (deepseek-v4-pro поверх якорного черновика) КУПЛЕН, $0
A1 черновик + детерминированные флаги (батарея + канон-гейт) → фиксер «ВМЕСТО редактора»
A2 dspro-однопроходка УРАВНЕННОГО мандата (строка 153 бэклога) без редактора вовсе
A3 черновик + СМЫСЛОВОЙ LLM-критик → фиксер ← СТАВКА ВЛАДЕЛЬЦА H-2б «ВМЕСТО редактора»
A4 A0 + канон-фиксер ПОСЛЕ «ПОСЛЕ редактора»
A6 dspro-ЧЕРНОВИК + оба контура ← носитель H-1, база куплена эксп-22
семейство: A1/A0 · A2/A0 · A3/A0 · A4/A0 · A6/A0 Холм по пяти
```
«До редактора» не мерится сознательно: перепис затирает любую починку.
**Д3 — en→ru, 16 единиц Кристоффа:** боевая связка · dspro-однопроходка уравненного мандата ·
черновик + оба контура · **E6 второй редактор `glm-5`** · контроль `E0 vs D0`. Холм по пяти.
**Д6 — ja→ru, 9 единиц:** связка · однопроходка · контроль. **Статус — РАЗВЕДКА, объявлен здесь.**
### Д0.6 Нормы рига, принятые этой фазой (каждая куплена дорого прошлым паком)
* **Декой в каждой пачке**; сессия, не поймавшая его отрицательным знаком, аннулируется целиком.
* **Шумовой пол — парой, чьи половины судят РАЗНЫЕ сессии.** Обе половины — ОТДЕЛЬНЫЕ генерации;
боевая клетка половиной пола не берётся (в эксп-23 `CTRLfloorA ≡ T1` в 16/16, и контроль был
пуст). Порог берётся из своего пола без ручной поправки: пол, судимый разными сессиями, ловит
и межсессионную компоненту, которая в эксп-23 занижала порог на 19%.
* **Все армы единицы — в одном задании одной сессии** (урок Sol §16 эксп-22, подтверждён с двух
сторон: нарушив — замер разваливается, соблюдя — работает на том же судье).
* **Судейские сессии регистрируются ДО запуска** (`eval/dovodka/runs.py`, кап 80, атомарный
`O_CREAT|O_EXCL`-замок с проверкой живости PID, селфтест 16/16). Гонка сессий по одному токену
механически невозможна — в эксп-23 она стоила расхождения голосов и сырья на 5 единицах из 32.
* **Клетка с `finish=length` в судейскую пачку НЕ допускается** — ни боевым армом, ни половиной
пола. Класс замерен: в эксп-22 таких судимых слотов семь, две из них в шумовом полу обеих фаз,
одна в базе всех контрастов (§15 эксп-22).
* **Позиционный наклон** замеряется, вычитается и сторожится гейтом числом.
* **Донор декоя уравнен по армам**; судье запрещено сравнивать варианты между собой и читать их рядом.
* **Пар-промпты проходят механический гейт** (`eval/dovodka/promptdiff.py`): всё вне объявленных
пар-специфичных секций обязано совпадать с боевым zh побайтно, мандатные оговорки не теряются,
а каждое законное расхождение объявлено с причиной. Урок эксп-19 (арм конфаундирован неполным
зеркалом) закрыт механизмом, а не обещанием.
* **$0-детекторы получают свой контроль наравне с судьями.** Норма заведена этой фазой после того,
как приёмка замерила ложноположительную частоту канон-классификатора: 82.5% на случайных окнах
при наблюдённых 79% — прибор был слабее нулевой модели. **Всякий классификатор, чьё число идёт
в отчёт, обязан печатать рядом свою частоту срабатывания на случайном входе.**
### Д0.7 ⚠ Что внешняя подпись может и чего не может — объявляется ДО прогона
Внешний судья вне семьи Claude имеет шумовой пол sd 5.05 и порог различимости 3.53 (§12в). Это
значит, что под ОТРИЦАТЕЛЬНЫМ выводом его подпись почти автоматична: возразить он мог бы только
эффекту вчетверо больше замеренного. Поэтому заранее:
* для прохода `tier` (перевесы 0.19…0.50) внешняя подпись будет означать «внешний судья тоже не
увидел», а НЕ «внешний судья подтвердил равенство»;
* для несущего вердикта Д4 по H-2б, где ставка — тонкий выигрыш, внешний контур не добавит и не
отнимет; его ценность — поймать КАТАСТРОФУ, которой внутрисемейные судьи не заметили.
Это ограничение прибора, а не отговорка: сказано до того, как числа получены.
### Д0.8 Смета по ЗАМЕРЕННЫМ ценам и фазовые потолки (`eval/dovodka/plan.py`)
```
ФД-A Д4 edit-контур zh (A1·A2·A3·A4, n=32 + свой пол) 0.877
ФД-B Д3 en→ru несущая 0.556
ФД-C Д6 ja→ru разведка (n=9) 0.154
ФД-D Д7 самооценка 0.050
ФД-F H-1: A6, dspro-черновик + оба контура (n=16) 0.280
ФД-G H-4: E6, второй редактор glm-5 на en (n=16) 0.200
ФД-E Д1 добивка gemini, 16 клеток 2.352
ИТОГО 4.470 / 4.50 · резерв 0.030
```
Цены — медианы по СЫРЬЮ уже купленных клеток обоих паков, не по прайсу. Прайс DeepSeek пере-снят
живьём 10.08 и **не изменился** (flash $0.14/$0.28, pro $0.435/$0.87); вендорская сноска о
готовящемся значительном повышении на странице присутствует и учтена оговоркой, а не числом.
**Резерв $0.03 — это 0.7% пакета, и сессия объявила это риском ДО покупок.** Ре-гены эхо-мины и
ретраи в паках 22/23 съедали до +16% сверх сметы фазы. Честным потолком без стопов посреди фазы
сессия называла $4.80; владелец выбрал $4.50. **Срабатывание проекционного гарда = СТОП и вопрос
владельцу**, а не сокращение оси решением сессии.
### Д0.9 Прогнозы (калибруют удивление; совпадение НЕ цель)
1. **A3 (смысловой критик) НЕ побьёт A0 различимо.** Основание: все замеренные различия между
близкими редакторскими контурами лежат в 0.22.5, а MDE оси 1.29. Прогноз конкретен: перевес
A3/A0 ляжет в полосу 1.0…+1.0. **Если владелец прав и ставка сыграет, я ошибусь — и это
лучший исход фазы.**
2. **A4 (канон-фиксер ПОСЛЕ переписа) даст лучшее покрытие канона из всей панели**, не двигая
судейский перевес: он чинит ось, по которой боевой редактор худший в эксп-22 (0.882).
3. **A1 (флаги) окажется «не хуже» по правилу Д0.4, но дешевле в разы.** Основание: флаговый
контур эксп-20 чинил по указанному месту за ~$0.0002.
4. **H-1 подтвердится частично:** A6 (dspro-черновик + контур) побьёт A1/A3 на flash-черновике,
но не побьёт A0. Основание — находка эксп-22 §13: редактор работает компрессором различий
черновика (разброс 7.25 → 2.12).
5. **На en контроль редактора снова окажется около нуля** (эксп-22: +0.00 при n=6), но теперь при
n=16 и объявленной цели 2.00 это будет замер, а не отсутствие мощности.
6. **`gemini-3.1-pro-preview` не отдаст 16 клеток с первой волны.** Прогноз: 503-серии повторятся,
потребуется ≥2 суток окна; собрано будет 1215 из 16.
### Д0.10 Чего эта фаза НЕ меряет
Топология «черновик → редактор» (решена эксп-21) · банк как фактор · выбор жильца черновой роли
(решён эксп-22) · качество прозы `gemini` как продуктовый выбор — при $221 за книгу против $8 у
боевого он невозможен ни при каком исходе, меряется только гипотеза «аутлаер прозы» эксп-04.

311
eval/dovodka/canon.py Normal file
View file

@ -0,0 +1,311 @@
#!/usr/bin/env python3
"""Д5 — КАНОН-ВСКРЫТИЕ: где именно боевой редактор теряет покрытие банка. $0.
Политика владельца 10.08, зафиксирована словом: «Банк существует, чтобы термины единообразно
переводились. Всё. Художественность текста это отдельный вопрос.» потеря канона = дефект
БЕЗУСЛОВНО, судейских опросов об «уместности замены» не бывает. Задача этого файла не спорить
о том, дефект ли это, а дать фиксеру МЕХАНИЗМ: перечислить места потери и назвать класс каждой.
Замер идёт на купленном сырье эксп-22, денег не стоит и ничего не покупает.
ДВЕ РАЗНЫЕ ВЕЛИЧИНЫ, и путать их нельзя:
1. ПОКРЫТИЕ доля канонной формы от числа упоминаний В ИСХОДНИКЕ (метрика `bank.coverage`).
Именно её печатали эксп-21/22. Она падает и когда термин передан ДРУГИМ переводом, и когда
он заменён местоимением а второе есть норма русской прозы там, где китайская повторяет имя.
2. ЕДИНООБРАЗИЕ то, ради чего банк существует по слову владельца: один термин передаётся
ОДНОЙ формой. Меряется подменой РОДОВОГО слова при неизменном якоре («дом Цинь» вместо
«род Цинь»), решается счётом по закрытому списку родовых и подпирается цитатой.
ЧЕГО ЗДЕСЬ БОЛЬШЕ НЕТ, И ПОЧЕМУ. Первая редакция классифицировала КАЖДОЕ место потери на
«исчез / парафраз / другой перевод» по выровненному окну редактуры. Адверсариальное ревью её
сняло, и правильно: (а) выравнивание не работает окно НИ В ОДНОМ из 24 мест не содержало
канона, хотя в полном тексте редактуры он стоит 818 раз; (б) класс «парафраз» присваивался
регексом, который срабатывает на 84% ПРОИЗВОЛЬНЫХ окон того же текста, то есть наблюдённые 79%
были НИЖЕ нулевой модели. Вывод «массовый класс парафраз» на таком приборе не стоит, и он снят.
Список мест остался как СЫРЬЁ для фиксера (где искать), но классом он больше не размечается и
числом по классам не подводится.
Запуск: eval/.venv/bin/python eval/dovodka/canon.py [--full] [--term <иероглиф>]
"""
from __future__ import annotations
import difflib
import importlib.util
import json
import re
import sys
from pathlib import Path
REPO = Path("/home/ubuntu/projects/textmachine")
OUT = Path.home() / "books" / "dovodka"
for p in ("tenant_panel", "role_topology"):
sys.path.insert(0, str(REPO / "eval" / p))
def _load(name: str, path: Path):
spec = importlib.util.spec_from_file_location(name, path)
mod = importlib.util.module_from_spec(spec)
sys.modules[name] = mod
spec.loader.exec_module(mod)
return mod
PAN = _load("panel22", REPO / "eval" / "tenant_panel" / "panel.py")
BANK = _load("bank22", REPO / "eval" / "tenant_panel" / "bank.py")
ED = _load("editors22", REPO / "eval" / "tenant_panel" / "editors.py")
MAT = PAN.M # material.py эксп-22
SENT = re.compile(r"[^.!?…»\n]+[.!?…»]*\s*")
# ⚠ ЗАКРЫТЫЙ СПИСОК РОДОВЫХ СЛОВ — данные КНИГИ, а не движка. Полигонный замер вправе знать
# лексику своего материала (CLAUDE.md: «тест-данные пары легитимны»); в общий пар-слой это
# не уходит. Классы заданы по канонам банка: то, чем в русском переводе называют 家 (клан/род).
GENERICS = {
"род": ("род", "дом", "семья", "семейство", "клан", "фамилия", "поместье"),
}
def GENERIC_CLASS(mod: str) -> tuple[str, ...]: # noqa: N802
for stem, cls in GENERICS.items():
if mod.lower().startswith(stem[:3]) or mod.lower() in cls:
return cls
return ()
def sents(t: str) -> list[str]:
return [s for s in SENT.findall(t or "") if s.strip()]
def best_match(s: str, edit_s: list[str], i: int, n_draft: int) -> int:
"""Индекс предложения редактуры, лучше всего отвечающего предложению черновика.
Первая редакция выравнивала `difflib`-опкодами по СПИСКУ предложений. На полном переписе это
не работает: почти всё уходит в один опкод `replace` на пол-текста, и окно для всех мест
единицы получалось одно и то же (поймано глазами при первом же прогоне все «исчез» на
единице `41599f30df` показывали одну и ту же реплику). Здесь ищется ближайшее по содержанию
предложение в ПОЛОСЕ вокруг пропорциональной позиции: редактор порядок сохраняет, поэтому
полоса законна, а сходство внутри неё решает.
"""
if not edit_s:
return -1
c = int(i / max(1, n_draft) * len(edit_s))
band = max(4, int(0.15 * len(edit_s)))
lo, hi = max(0, c - band), min(len(edit_s), c + band + 1)
key = s.strip()
best, bj = -1.0, c if c < len(edit_s) else len(edit_s) - 1
for j in range(lo, hi):
r = difflib.SequenceMatcher(None, key, edit_s[j].strip(), autojunk=False).ratio()
if r > best:
best, bj = r, j
return bj
def window(edit_s: list[str], j: int) -> str:
if j < 0:
return ""
return " ".join(edit_s[max(0, j - 1):min(len(edit_s), j + 2)])
# ⚠ Классификация ужесточена после ЧТЕНИЯ первых мест (мандат D39.61: вскрыть единицы до вывода).
# Первая редакция звала «другим переводом» ЛЮБОЕ заглавное слово в окне — и записала в этот класс
# `Этот человек`, `Кошмар`, `Перед такой`, то есть соседние слова предложения. Так класс «другой
# перевод» набрал 14 мест из 24 и был бы вписан в отчёт неверным. Правило переписано:
# `другой` — в окне стоит форма, ПОХОЖАЯ на канон (Цин Фэн / Цинь Фен / Qin Feng), но не он:
# это и есть расхождение перевода термина, которое банк обязан устранять;
# `парафраз` — на месте термина референциальная замена (местоимение, эпитет, родовое слово);
# `исчез` — ни того, ни другого: место есть, термина и его следа нет.
# ⚠ Второй элемент ОБЯЗАН начинаться с заглавной. Первая редакция клеила любое следующее слово, и
# список «конкурирующих форм» наполнился мусором вида «Цинь в», «Фэн-лао до», «Тяньянчэне был» —
# то есть склонениями и предлогами, а не переводами. Поймано чтением собственного вывода.
TOKEN = re.compile(r"(?<![\\"])([А-ЯЁA-Z][\w'-]{1,}(?:[- ][А-ЯЁA-Z][\w'-]{1,})?)")
EPITHET = re.compile(r"(?<!\w)(он|она|оно|они|его|её|их|тот|та|этот|эта|"
r"юнош\w+|девушк\w+|старик\w+|старейшин\w+|господин\w*|госпож\w+|"
r"молод\w+\s+господин\w*|наследник\w*|глав\w+|отец|мать|сын|дочь|"
r"человек\w*|мужчин\w+|женщин\w+|"
r"род\w*|семь\w+|клан\w*|техник\w+|искусств\w+|ступен\w+|уровн\w+|"
r"стади\w+|ядр\w+|фундамент\w*|пилюл\w+)(?!\w)", re.I)
def _near(a: str, b: str) -> float:
return difflib.SequenceMatcher(None, a.lower(), b.lower(), autojunk=False).ratio()
def classify(win: str, rx: str, canon: str) -> tuple[str, str]:
"""(класс, улика). Улика печатается рядом с цитатой — вердикт проверяем чтением."""
if not win:
return "исчез", ""
if re.search(rx, win, re.I):
return "есть", ""
# похожая, но не канонная форма того же термина
near = [(t, _near(t, canon)) for t in TOKEN.findall(win)]
near = [(t, r) for t, r in near if 0.55 <= r < 1.0 and len(t) >= 3]
if near:
t, r = max(near, key=lambda x: x[1])
return "другой", f"{t} (сходство {r:.2f})"
m = EPITHET.search(win)
if m:
return "парафраз", m.group(0)
return "исчез", ""
def main() -> int:
full = "--full" in sys.argv
only = sys.argv[sys.argv.index("--term") + 1] if "--term" in sys.argv else ""
bank = BANK.PR.bank()
units = MAT.units_zh()
# словарь альтернативных написаний: латиница/иная транскрипция того же имени
rows, per_term, per_class = [], {}, {}
gross_loss = gross_gain = 0
tot_src = tot_hit_draft = tot_hit_edit = 0
# ⚠ Клетка R0 с `finish=length` даёт «потерю канона», которая на деле есть ОБРЫВ: термины
# после места обрыва отсутствуют не потому, что редактор их убрал. Такие единицы считаются
# отдельно (эксп-22 §15: класс замерен, на этой панели он покрывает единицу `41599f30df`).
trunc = set()
for u in units:
f = PAN.OUT / f"{PAN.tag_edit(PAN.ANCHOR, u['uid'])}.json"
if f.exists() and json.loads(f.read_text(encoding="utf-8")).get("finish") == "length":
trunc.add(u["uid"])
for u in units:
uid, src = u["uid"], u["source"]
if uid in trunc:
continue
draft = PAN.draft_text(PAN.ANCHOR, uid)
edit = ED.text_of("R0", uid) if hasattr(ED, "text_of") else PAN.edit_text(PAN.ANCHOR, uid)
if not (draft and edit):
continue
ds, es = sents(draft), sents(edit)
for term, v in bank.items():
if only and term != only:
continue
n = src.count(term)
if not n:
continue
rx = v["rx"]
hd = min(len(re.findall(rx, draft, re.I)), n)
he = min(len(re.findall(rx, edit, re.I)), n)
tot_src += n
tot_hit_draft += hd
tot_hit_edit += he
if he > hd:
gross_gain += he - hd # редактор канон ДОБАВИЛ
if he >= hd:
continue # редактор канон не терял
gross_loss += hd - he
# места: предложения черновика с канонной формой, чьё окно в редактуре его потеряло
places = []
for i, s in enumerate(ds):
if not re.search(rx, s, re.I):
continue
j = best_match(s, es, i, len(ds))
win = window(es, j)
cls, why = classify(win, rx, v["dst"])
if cls == "есть":
continue
places.append((cls, why, s.strip()[:150], win.strip()[:190]))
keep = places[: (hd - he)] or places
for cls, why, s, w in keep:
rows.append((uid, term, v["dst"], cls, why, s, w))
per_class[cls] = per_class.get(cls, 0) + 1
per_term.setdefault(term, {"dst": v["dst"], "n": 0})
per_term[term]["n"] += 1
print("Д5 — КАНОН-ВСКРЫТИЕ БОЕВОГО РЕДАКТОРА (R0 поверх якорного черновика, эксп-22, $0)\n")
print(f"единиц {len(units)} · из них ИСКЛЮЧЕНО по обрыву клетки R0: {len(trunc)} "
f"{sorted(trunc)} · разбирается {len(units) - len(trunc)}")
print(f"терминов в банке {len(bank)} · упоминаний в исходниках разбираемых единиц {tot_src}")
print(f"покрытие ЧЕРНОВИКА {tot_hit_draft}/{tot_src} = {tot_hit_draft / max(1, tot_src):.3f}"
f" · покрытие РЕДАКТУРЫ {tot_hit_edit}/{tot_src} = {tot_hit_edit / max(1, tot_src):.3f}"
f" · САЛЬДО {tot_hit_draft - tot_hit_edit}")
print(f"⚠ САЛЬДО — не потеря. ВАЛОВАЯ потеря {gross_loss} упоминаний, ВАЛОВОЙ прирост "
f"{gross_gain}: в части пар редактор канон ДОБАВИЛ там, где черновик его не держал.\n"
" Прежняя редакция печатала одно число «ПОТЕРЯ» рядом с числом мест и читалась так,\n"
" будто это одно и то же (поймано ревью). Набор фиксера ниже покрывает валовую потерю,\n"
" а не сальдо.")
print(f"\nмест, где канонная форма отсутствует в выровненном окне: {len(rows)}")
print(" ⚠ КЛАССАМИ НЕ РАЗМЕЧАЮТСЯ. Прежняя разбивка «исчез/парафраз/другой» снята ревью:\n"
" выравнивание ненадёжно, а класс «парафраз» слабее нулевой модели. Ниже — сырьё\n"
" для фиксера (где смотреть), а не измерение долей.")
print("\nПО ТЕРМИНАМ (что фиксер обязан уметь возвращать):")
print(f"{'термин':10s}{'канон':30s}{'мест потери':>12s}")
for t, d in sorted(per_term.items(), key=lambda kv: -kv[1]["n"]):
print(f"{t:10s}{d['dst']:30s}{d['n']:12d}")
print("\nМЕСТА (цитатой; окно приблизительное — сверять глазами, это подсказка, не вердикт):")
for uid, term, dst, _cls, _why, s, w in (rows if full else rows[:12]):
print(f"\n {uid} · {term} → «{dst}»")
print(f" черновик: …{s}")
print(f" редактура: …{w}")
if not full and len(rows) > 12:
print(f"\n … ещё {len(rows) - 12} мест, целиком — с флагом --full")
# ── ВТОРАЯ МЕТРИКА: ЕДИНООБРАЗИЕ, а не частота ─────────────────────────────────────────────
# ⚠ ПЕРВАЯ РЕДАКЦИЯ ЭТОЙ МЕТРИКИ НЕ РАБОТАЛА И БЫЛА СНЯТА АДВЕРСАРИАЛЬНЫМ РЕВЬЮ. Она искала
# «похожие на канон строки» по расстоянию Левенштейна и набирала склонения («Цао Ина») и чужих
# персонажей с общей фамилией («Цинь Вэньтянь»); настоящие нарушения — «дом Цинь» вместо «род
# Цинь» — не находила вовсе, потому что требовала заглавной буквы у первого слова. Ревьюер
# нашёл их РУКАМИ, и это и есть доказательство, что счётом их взять можно: они отличаются
# РОДОВЫМ СЛОВОМ при том же якоре.
#
# Правило теперь такое: у канона вида «<родовое> <Имя>» якорь — имя, а родовое слово меняется.
# Считаем распределение родовых слов при якоре ПО ЗАКРЫТОМУ СПИСКУ и зовём нарушением всякое,
# чей корень отличается от канонного. Список — данные КНИГИ (полигонный замер, не движок).
print("\n" + "" * 78)
print("ЕДИНООБРАЗИЕ: каким РОДОВЫМ словом передан термин при том же якоре")
viol, seen_any = [], False
for term, v in bank.items():
canon = v["dst"]
toks = canon.split()
if len(toks) != 2:
continue
mod, anchor = toks
gen = GENERIC_CLASS(mod)
if not gen:
continue
seen_any = True
rx = re.compile(r"(?<![\w-])(" + "|".join(gen) + r")\w*\s+" + re.escape(anchor[:4]) + r"\w*",
re.I | re.U)
cnt: dict[str, int] = {}
places: dict[str, list[tuple[str, str]]] = {}
for u in units:
if u["uid"] in trunc:
continue
txt = ED.text_of("R0", u["uid"]) or ""
for m in rx.finditer(txt):
g = m.group(1).lower()
cnt[g] = cnt.get(g, 0) + 1
if not g.startswith(mod.lower()[:3]):
places.setdefault(g, []).append(
(u["uid"], txt[max(0, m.start() - 60):m.end() + 60].replace("\n", " ")))
if not cnt:
continue
tot = sum(cnt.values())
line = " · ".join(f"{g} {n}" for g, n in sorted(cnt.items(), key=lambda kv: -kv[1]))
print(f"\n {term} → канон «{canon}» (якорь «{anchor}»): {line}")
for g, ps in places.items():
share = cnt[g] / tot
viol.append((term, canon, g, cnt[g], share))
print(f" ⛔ КОНКУРИРУЮЩЕЕ РОДОВОЕ «{g} {anchor}» — {cnt[g]} из {tot} "
f"({share * 100:.0f}%)")
for uid, q in ps[:2]:
print(f" {uid}: …{q.strip()}")
if not seen_any:
print(" канонов вида «<родовое> <Имя>» в банке нет")
print(f"\nНАРУШЕНИЙ ЕДИНООБРАЗИЯ (конкурирующее родовое при том же якоре): {len(viol)}")
print("⚠ ЧТО ЭТА МЕТРИКА ЛОВИТ И ЧЕГО НЕ ЛОВИТ. Ловит подмену РОДОВОГО слова при неизменном\n"
" якоре — класс, ради которого банк и существует по слову владельца. НЕ ловит: подмену\n"
" самого якоря (транскрипция имени), расхождения у одно-словных канонов и у канонов\n"
" вида «прилагательное + существительное» (金丹 «Золотое ядро» → «Золотая пилюля»:\n"
" найдено ЧТЕНИЕМ на единице `c73f4857e7`, счётом здесь не берётся). Список родовых —\n"
" закрытый и книго-специфичный; его полнота и есть граница метрики.")
OUT.mkdir(parents=True, exist_ok=True)
(OUT / "canon-dissect.json").write_text(json.dumps(
[dict(uid=a, term=b, dst=c, cls=d, why=e, draft=f, edit=g)
for a, b, c, d, e, f, g in rows],
ensure_ascii=False, indent=1), encoding="utf-8")
print(f"\nперсист → {OUT / 'canon-dissect.json'}")
return 0
if __name__ == "__main__":
sys.exit(main())

173
eval/dovodka/material_ja.py Normal file
View file

@ -0,0 +1,173 @@
#!/usr/bin/env python3
"""Д6 — МАТЕРИАЛ ПАРЫ ja→ru: гардрейл, гейт прав, отбор единиц. $0.
Источник: `~/books/isekai_majutsushi_jp.txt` (файл владельца, вне git правило Р8). Площадка
`novel18.syosetu.com`, то есть срез ЗАВЕДОМО взрослый; аннотация книги сама называет 洗脳 и 暴力.
ГАРДРЕЙЛ ПЕРВЫМ, ДО ЛЮБОГО ДРУГОГО ДЕЙСТВИЯ. Гардрейл владельца (вынесен в `eval/README.md`):
**18+ уровень 3 несовершеннолетние в сексуальном контексте не обрабатывать и не анализировать
ни в каком виде.** Здесь он реализован механически: чанк, где ПЛОТНО совпадают маркеры возраста и
явные сексуальные маркеры, помечается и материал целиком уходит в СТОП с пингом владельцу. Ни один
такой чанк не печатается и дальше по конвейеру не идёт печатается только его номер и счёт
маркеров. Детектор существует, чтобы ИСКЛЮЧАТЬ, и его срабатывание не повод читать внимательнее.
Дальше та же лестница, что у эксп-22 §0.2, и по тем же причинам:
1. гейт ПРАВ (маршрутизация по договорам провайдеров, не по поведению модели): у `deepseek` и
`zai` класс `sexually-explicit` FORBIDDEN, поэтому верхний дециль по плотности эротических
маркеров снимается ДО отбора единиц;
2. отбор единиц детерминированный: дедуп по подписи, ключ артефакта хеш источника, сортировка
по (длина, uid), из середины распределения длин;
3. манифест: единицы прикалываются к файлу, иначе докачка материала молча сдвинет уже
купленный замер (ровно это случилось с эксп-22 и чинилось в паке 23).
Запуск: eval/.venv/bin/python eval/dovodka/material_ja.py [--units]
"""
from __future__ import annotations
import hashlib
import json
import re
import sys
from pathlib import Path
SRC = Path.home() / "books" / "isekai_majutsushi_jp.txt"
OUT = Path.home() / "books" / "dovodka"
MANIFEST = OUT / "manifest-ja.json"
TARGET_CHARS, MIN_CHARS, N_UNITS = 1100, 700, 9 # ja плотнее zh на знак: цель короче
# ── маркеры гардрейла ─────────────────────────────────────────────────────────────────────────
AGE = re.compile(r"(小学生|中学生|高校生|幼女|幼児|少女|少年|児童|子供|子ども|ロリ|"
r"[0-9-9一二三四五六七八九十]{1,2}\s*[歳才])")
SEX = re.compile(r"(性行為|セックス|挿入|射精|愛撫|陰茎|陰部|膣|乳房|裸体|全裸|絶頂|喘|"
r"犯[すされし]|強姦|レイプ|淫|媚薬|発情)")
# Эротическая плотность для ГЕЙТА ПРАВ — шире, чем гардрейл: это про договоры провайдеров.
EROTIC = re.compile(r"(キス|口づけ|抱[きく]しめ|裸|胸|尻|太もも|下着|色気|艶|情事|房事|"
r"性的|欲情|快感|喘|淫|媚薬|発情|愛撫|挿入|射精|セックス|性行為)")
def text() -> str:
if not SRC.exists():
raise SystemExit(f"⛔ материала нет: {SRC}")
return SRC.read_text(encoding="utf-8", errors="replace")
def chapters() -> list[dict]:
"""Главы по разделителю площадки; заголовок остаётся в теле — он часть материала."""
raw = text()
parts = re.split(r"\n=+\n", raw)
out = []
for i, p in enumerate(parts):
p = p.strip()
if len(p) >= MIN_CHARS:
out.append(dict(idx=i, text=p))
return out
def chunks() -> list[dict]:
"""Абзацные чанки; абзац не рвётся, хвост главы прирастает к предыдущему (правило эксп-22)."""
out = []
for ch in chapters():
made, buf, size = [], [], 0
for para in ch["text"].split("\n"):
para = para.strip()
if not para:
continue
buf.append(para)
size += len(para)
if size >= TARGET_CHARS:
made.append(dict(text="\n".join(buf), chapter=ch["idx"]))
buf, size = [], 0
if size:
tail = "\n".join(buf)
if size >= MIN_CHARS or not made:
made.append(dict(text=tail, chapter=ch["idx"]))
else:
made[-1]["text"] += "\n" + tail
out += made
return out
def uid_of(s: str) -> str:
return hashlib.sha1(s.encode("utf-8")).hexdigest()[:10] # noqa: S324
def guardrail(cs: list[dict]) -> list[int]:
"""Индексы чанков КЛАССА, КОТОРЫЙ НЕ ОБРАБАТЫВАЕТСЯ. Содержимое не печатается никогда."""
hits = []
for i, c in enumerate(cs):
t = c["text"]
a, s = len(AGE.findall(t)), len(SEX.findall(t))
if a and s and (a + s) >= 4:
hits.append(i)
return hits
def main() -> int:
cs = chunks()
print(f"Д6 МАТЕРИАЛ ja→ru · источник {SRC.name} · знаков {len(text())} · чанков {len(cs)}")
print("\n=== ГАРДРЕЙЛ 18+ УРОВНЯ 3 (проверяется ПЕРВЫМ) ===")
bad = guardrail(cs)
if bad:
print(f"⛔ СТОП. Чанков с плотным совпадением возрастных и явных сексуальных маркеров: "
f"{len(bad)} из {len(cs)} (номера: {bad[:12]}{'' if len(bad) > 12 else ''}).")
print(" Содержимое НЕ печатается и дальше не идёт. Материал непригоден по гардрейлу\n"
" владельца; замена без его слова НЕ ищется (промт: «непригоден по пробе → СТОП\n"
" и пинг»).")
if "--owner-sanctioned-exclude" not in sys.argv:
print("\n Детектор КОНСЕРВАТИВЕН намеренно: 少年/少女/子供 — частотные слова японской\n"
" прозы, и совпасть с эротическим маркером они могут безобидно. Развести это\n"
" можно только ЧТЕНИЕМ помеченных мест, а читать их правило запрещает — значит\n"
" решает не сессия. Обход существует и требует слова владельца:\n"
" --owner-sanctioned-exclude помеченные чанки выбрасываются механически,\n"
" остальные 181 идут в отбор; в отчёт пишется,\n"
" сколько и почему выброшено.\n"
" Прекращаю обработку источника.")
return 1
print(f"\n ⚠ ОБХОД ПО СЛОВУ ВЛАДЕЛЬЦА: {len(bad)} помеченных чанков выброшены "
"механически, без чтения.")
cs = [c for i, c in enumerate(cs) if i not in set(bad)]
print(f" осталось чанков: {len(cs)}")
else:
print(f"[OK ] срабатываний нет: 0 из {len(cs)} чанков")
print("\n=== ГЕЙТ ПРАВ (верхний дециль эротической плотности снимается ДО отбора) ===")
dens = sorted(((len(EROTIC.findall(c["text"])) / max(1, len(c["text"])) * 1000, i)
for i, c in enumerate(cs)), reverse=True)
cut = max(1, len(cs) // 10)
dropped = {i for _, i in dens[:cut]}
print(f"снято чанков {len(dropped)} из {len(cs)} (дециль); плотность верхнего "
f"{dens[0][0]:.2f}/1000 знаков, порога дециля {dens[cut - 1][0]:.2f}, медиана "
f"{dens[len(dens) // 2][0]:.2f}")
pool = [c for i, c in enumerate(cs) if i not in dropped]
print("\n=== ОТБОР ЕДИНИЦ (детерминированный, методика эксп-21 §0) ===")
uniq: dict[str, dict] = {}
for c in pool:
u = uid_of(c["text"])
uniq.setdefault(u, dict(uid=u, source=c["text"], chapter=c["chapter"]))
ordered = sorted(uniq.values(), key=lambda x: (len(x["source"]), x["uid"]))
mid = len(ordered) // 2
lo = max(0, mid - N_UNITS // 2)
units = ordered[lo:lo + N_UNITS]
ls = [len(u["source"]) for u in units]
print(f"пул {len(ordered)} уникальных · выбрано {len(units)} из середины распределения длин")
print(f"знаков {min(ls)}{max(ls)}, медиана {sorted(ls)[len(ls) // 2]} · "
f"глав {len({u['chapter'] for u in units})}")
for u in units:
print(f" {u['uid']} гл.{u['chapter']:<4d} знаков {len(u['source']):5d}")
if "--units" in sys.argv:
OUT.mkdir(parents=True, exist_ok=True)
MANIFEST.write_text(json.dumps(
dict(source=SRC.name, n_chunks=len(cs), dropped_rights=len(dropped),
ja=dict(uids=[u["uid"] for u in units])), ensure_ascii=False, indent=1),
encoding="utf-8")
print(f"\nманифест → {MANIFEST} (единицы приколоты; докачка их больше не сдвинет)")
else:
print("\n(манифест НЕ записан — прогнать с флагом --units, когда состав утверждён)")
return 0
if __name__ == "__main__":
sys.exit(main())

139
eval/dovodka/money_d.py Normal file
View file

@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""КАССА ФАЗЫ Д. $0 сама по себе; через неё идут ВСЕ платные вызовы фазы.
Не копия кассы эксп-22, а её НАСТРОЙКА через `configure` тот же приём, которым пользовался пак
23. Механизм там проверенный и дорого доставшийся: фриз-гейт (покупка отказывает коду, который не
закоммичен или отличается от закоммиченного), атомарный замок клетки `O_CREAT|O_EXCL`, проекционный
сторож перед КАЖДЫМ вызовом по цене ИМЕННО ЭТОЙ модели, два независимых пути счёта. Копирование
размножило бы будущие расхождения вместо того, чтобы их ловить.
ПОТОЛОК ФАЗЫ САНКЦИЯ ВЛАДЕЛЬЦА $4.50 (10.08). История цифры: промт называл $10.00, владелец при
запуске сказал $4.00, затем поднял до $4.50, когда приёмка другого модельного семейства нашла, что
дизайн не отвечает на H-1 и H-4, а починка стоит $0.48.
РЕЗЕРВ ФАЗЫ $0.03 0.7% пакета, и это объявлено риском ДО покупок (Д0.8). Ре-гены эхо-мины и
ретраи в паках 22/23 съедали до +16% сверх сметы фазы. Сессия называла честным потолком $4.80;
владелец выбрал $4.50. Поэтому правило исполнения жёсткое и записано здесь, а не в прозе:
**срабатывание проекционного гарда = СТОП и вопрос владельцу.** Резать ось, панель или кандидата
решением сессии ЗАПРЕЩЕНО заказом; поднимать потолок себе тем более (пак 23 так и сделал, и это
стоило ему пункта CONFIRM/DENY).
Запуск: money_d.py [--report|--selftest]
"""
from __future__ import annotations
import sys
from pathlib import Path
REPO = Path("/home/ubuntu/projects/textmachine")
ZONE = Path(__file__).resolve().parent
sys.path.insert(0, str(ZONE))
sys.path.insert(0, str(REPO / "eval" / "tenant_panel"))
sys.path.insert(0, str(REPO / "eval" / "role_topology"))
def _load(name: str, path: Path):
"""⚠ Чужое — ТОЛЬКО по пути. `import money` находил кассу эксп-22 или сам себя, смотря по
порядку `sys.path.insert`; пак 23 напоролся на это четырежды, и один раз молча."""
import importlib.util # noqa: PLC0415
spec = importlib.util.spec_from_file_location(name, path)
mod = importlib.util.module_from_spec(spec)
sys.modules[name] = mod
spec.loader.exec_module(mod)
return mod
M22 = _load("money22", REPO / "eval" / "tenant_panel" / "money.py")
ROSTER = _load("roster22", REPO / "eval" / "tenant_panel" / "roster.py")
OUT = Path.home() / "books" / "dovodka"
# Теги покупок по фазам. ⚠ Новый тег вписывается СЮДА тем же коммитом, что и код, который его
# заводит: расход мимо глоба невидим для кассы, и эксп-21 напоролся на это трижды, а последний
# раз гейт заниженное число ещё и СЕРТИФИЦИРОВАЛ.
TAGS = {
"ФД-A": ("dv-a-*.json",), # Д4 edit-контур zh: A1 A2 A3 A4 + свой шумовой пол
"ФД-B": ("dv-b-*.json",), # Д3 en→ru несущая ось: банк, контам, армы, пол
"ФД-C": ("dv-c-*.json",), # Д6 ja→ru разведка
"ФД-D": ("dv-d-*.json",), # Д7 микро-проба самооценки
"ФД-E": ("dv-e-*.json",), # Д1 добивка gemini-3.1-pro-preview
"ФД-F": ("dv-f-*.json",), # H-1: A6 dspro-черновик + оба контура
"ФД-G": ("dv-g-*.json",), # H-4: E6 второй редактор на en
}
# ⚠ Потолки фаз = смета `plan.py` плюс МИНИМАЛЬНЫЙ запас, так чтобы их СУММА не превышала
# пакетный потолок. Первая редакция округляла каждый вверх до цента и дала сумму 4.54 > 4.50 —
# то есть фазовые гарды в совокупности разрешали пробить пакетный. Поймано селфтестом кассы
# ДО первой покупки; ровно этот класс («касса охраняет не ту границу») стоил эксп-21 денег трижды.
CEILINGS = {"ФД-A": 0.885, "ФД-B": 0.560, "ФД-C": 0.155, "ФД-D": 0.055,
"ФД-E": 2.355, "ФД-F": 0.283, "ФД-G": 0.202}
PACK_CEILING = 4.50
M22.configure(ZONE=ZONE, OUT=OUT, TAGS=TAGS, CEILINGS=CEILINGS,
PACK_CEILING=PACK_CEILING, GLOB_ALL="dv-*.json", ROSTER=ROSTER)
Guarded, Pack, purchase, model_expect = M22.Guarded, M22.Pack, M22.purchase, M22.model_expect
def report() -> None:
print(f"{'фаза':7s}{'потрачено':>12s}{'потолок':>10s} что покупает")
tot = 0.0
what = {"ФД-A": "Д4 edit-контур zh", "ФД-B": "Д3 en→ru несущая", "ФД-C": "Д6 ja разведка",
"ФД-D": "Д7 самооценка", "ФД-E": "Д1 gemini", "ФД-F": "H-1 (A6)", "ФД-G": "H-4 (E6)"}
for ph in TAGS:
sp = Guarded(ph).spent()
tot += sp
mark = " ⛔ ПРОБОЙ" if sp > CEILINGS[ph] + 1e-9 else ""
print(f"{ph:7s}{sp:12.6f}{CEILINGS[ph]:10.2f} {what[ph]}{mark}")
print(f"{'ПАК':7s}{tot:12.6f}{PACK_CEILING:10.2f} резерв {PACK_CEILING - tot:.6f}")
def selftest() -> int:
"""Обязан пройти ДО первой покупки фазы."""
bad = 0
def ck(n: str, ok: bool, d: str = "") -> None:
nonlocal bad
bad += not ok
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {n}" + (f" {d}" if d else ""))
ck("касса смотрит в СВОЁ сырьё", M22.OUT == OUT, str(M22.OUT))
ck("покупки уходят в каталог фазы", M22.BUY.OUT == OUT, str(M22.BUY.OUT))
ck("фриз-гейт сторожит зону фазы", M22.ZONE == ZONE, str(M22.ZONE))
ck("сумма фазовых потолков не выше пакетного",
sum(CEILINGS.values()) <= PACK_CEILING + 1e-9,
f"{sum(CEILINGS.values()):.2f}{PACK_CEILING:.2f}")
for ph, globs in TAGS.items():
for g in globs:
ck(f"{ph}: тег {g} покрыт глобом пака", g.startswith("dv-"))
# цена обязана считаться для КАЖДОЙ модели, которую фаза может позвать
for m in ("deepseek-v4-flash", "deepseek-v4-pro", "glm-5", "gemini-3.1-pro-preview"):
try:
ck(f"цена считается: {m}", M22.BUY.cost(m, 4500, 0, 2500, 1000, 8000) > 0)
except Exception as e: # noqa: BLE001
ck(f"цена считается: {m}", False, f"{type(e).__name__}: {e}")
# проекционный гард обязан ОТКАЗЫВАТЬ, когда ожидание пробивает потолок
g = Guarded("ФД-D")
g.ceiling = 0.0
ck("проекционный гард отказывает при нулевом потолке", not g.afford()[0])
# и обязан видеть цену ДОРОГОЙ модели, а не p95 прошлого (регрессия на пробой Ф1 эксп-22)
g2 = Guarded("ФД-D")
g2.ceiling = g2.spent() + 0.05
g2.next_model = "gemini-3.1-pro-preview"
ck("гард видит цену дорогой модели, а не прошлое",
not g2.afford()[0], f"ожидание ${model_expect('gemini-3.1-pro-preview'):.4f}")
# фриз-гейт обязан отвергнуть покупку из НЕ-файла
try:
exec(compile("import money22; money22._refuse_unfrozen()", "<s>", "exec"), # noqa: S102
{"__name__": "x"})
ck("фриз-гейт отвергает покупку из НЕ-файла", False)
except SystemExit:
ck("фриз-гейт отвергает покупку из НЕ-файла", True)
print(f"\n{'КАССА ГОДНА' if not bad else f'ПРОВАЛОВ: {bad}'}")
return bad
if __name__ == "__main__":
a = sys.argv[1:] or ["--report"]
if a[0] == "--selftest":
sys.exit(1 if selftest() else 0)
report()

134
eval/dovodka/plan.py Normal file
View file

@ -0,0 +1,134 @@
#!/usr/bin/env python3
"""СМЕТА ФАЗЫ Д ПО ЗАМЕРЕННЫМ ЦЕНАМ КЛЕТОК. $0.
Почему скриптом, а не в столбик. Пак 23 поднял потолок фазы, сославшись на «панель $1.844»
это была ПРОЕКЦИЯ `--c-plan` по медианам, а фактическая панель стоила $1.798638 и влезала в
исходный потолок. Смета, посчитанная руками и не пере-снимаемая, ровно так и ошибается. Здесь
каждая цена берётся из СЫРЬЯ уже купленных клеток (медиана по модели и роли), и любой читатель
пере-считает командой.
Потолок фазы САНКЦИЯ ВЛАДЕЛЬЦА $4.50 (10.08). История: промт называл $10.00, владелец при
запуске сказал $4.00, затем поднял до $4.50, когда приёмка другого модельного семейства нашла,
что дизайн не отвечает на H-1 и H-4, а починка стоит $0.48. Сессия называла честным потолком
без стопов посреди фазы $4.80; владелец выбрал $4.50 резерв $0.03, и срабатывание гарда есть
СТОП и вопрос владельцу, а не сокращение оси решением сессии.
Запуск: eval/.venv/bin/python eval/dovodka/plan.py [--gemini N]
"""
from __future__ import annotations
import collections
import json
import statistics as st
import sys
from pathlib import Path
T = Path.home() / "books" / "tenant-panel"
E = Path.home() / "books" / "editor-tier"
PACK_CEILING = 4.50
def measured() -> dict[tuple[str, str], float]:
"""Медиана $/клетка по (роль, модель) из КУПЛЕННОГО сырья обоих паков."""
acc: dict[tuple[str, str], list[float]] = collections.defaultdict(list)
for d, pat in ((T, "tp*.json"), (E, "et-*.json")):
for f in d.glob(pat):
try:
r = json.loads(f.read_text(encoding="utf-8"))
except (ValueError, OSError):
continue
if isinstance(r, dict) and r.get("cost_usd") and r.get("model"):
acc[(r.get("role") or "?", r["model"])].append(r["cost_usd"])
return {k: st.median(v) for k, v in acc.items() if len(v) >= 3}
def main() -> int:
m = measured()
n_gem = 16
if "--gemini" in sys.argv:
n_gem = int(sys.argv[sys.argv.index("--gemini") + 1])
draft = m[("draft", "deepseek-v4-flash")]
edit = m[("edit", "deepseek-v4-pro")]
gem = 0.14702 # ЕДИНСТВЕННАЯ состоявшаяся клетка Ф3 эксп-22, n=1
# Производные цены армов edit-контура. Обоснование множителей — в отчёте §Д0; они ПРОГНОЗ,
# и расхождение с фактом печатается при закрытии фазы как сверка сметы.
fixer = 0.60 * edit # точечная починка: тот же вход, выход много короче
critic = 2.14 * edit # «поиск ≈2.14× переписа» — цифра из заказа владельца
onepass = edit # однопроходка: вход короче, размышление длиннее — вничью
print("ЗАМЕРЕННЫЕ ЦЕНЫ (медиана по сырью обоих паков):")
for k in (("draft", "deepseek-v4-flash"), ("edit", "deepseek-v4-pro"),
("editor3", "glm-5"), ("edit", "glm-5.2"), ("edit", "gpt-5.6-terra"),
("edit", "grok-4.5")):
if k in m:
print(f" {k[0]:8s}{k[1]:22s}${m[k]:.5f}")
print(f" {'edit':8s}{'gemini-3.1-pro-preview':22s}${gem:.5f} ⚠ n=1, "
f"{gem / edit:.0f}× боевого редактора")
print("\nПРОИЗВОДНЫЕ (прогноз, сверяется с фактом при закрытии):")
print(f" фиксер ${fixer:.5f} (0.60× переписа)")
print(f" критик ${critic:.5f} (2.14× переписа — множитель из заказа владельца)")
rows = []
# ── Д4 edit-контур zh, 32 единицы (16 эксп-22 + 16 эксп-23); A0 уже куплен обоими паками
n4 = 32
d4 = n4 * (fixer + onepass + (critic + fixer) + fixer) + 16 * fixer
rows.append(("ФД-A", "Д4 edit-контур zh: A1 флаги→фиксер · A2 однопроходка · "
f"A3 критик→фиксер · A4 канон-фиксер, n={n4} + свой пол", d4))
# ── Д3 en ось, 16 единиц
n3 = 16
d3 = (0.005 + 0.004 # банк-майнинг + проба контаминации
+ n3 * draft + n3 * edit + n3 * onepass
+ n3 * (fixer + critic + fixer) + n3 * edit) # два контура + пол
rows.append(("ФД-B", f"Д3 en→ru несущая: банк · контам-проба · связка · однопроходка · "
f"оба edit-контура · пол, n={n3}", d3))
# ── Д6 ja разведка, 9 единиц
n6 = 9
d6 = 0.004 + n6 * (draft + edit + onepass + edit)
rows.append(("ФД-C", f"Д6 ja→ru разведка: контам-проба · связка · однопроходка · пол, n={n6}",
d6))
# ── Д7 самооценка
rows.append(("ФД-D", "Д7 микро-проба самооценки жильцов по трём книгам", 0.05))
# ── Д1 gemini
# ⚠ ДВА АРМА, ЗАВЕДЁННЫЕ ПРИЁМКОЙ ДРУГОГО МОДЕЛЬНОГО СЕМЕЙСТВА. Без них фаза не отвечает на
# H-1 и H-4, и это нашёл не автор: H-1 говорит про «КАЧЕСТВЕННЫЙ черновик + точечный редактор»,
# а все армы Д4 ставят фиксер на flash-черновик — то есть на полумусор по самой гипотезе;
# H-4 про смену ПОРЯДКА жильцов на другой паре, а на en-оси редактор был ровно один.
rows.append(("ФД-F", "H-1: dspro-черновик эксп-22 (уже куплен, $0) + оба edit-контура, n=16 — "
"прямой носитель гипотезы «качественный черновик + точечный редактор»",
16 * (fixer + critic + fixer)))
rows.append(("ФД-G", "H-4: ВТОРОЙ редактор (glm-5) на en-оси поверх той же базы, n=16 — "
"без него порядок жильцов на en не проверяется вовсе", 16 * m[("editor3", "glm-5")]))
rows.append(("ФД-E", f"Д1 добивка gemini-3.1-pro-preview, {n_gem} клеток "
f"(503-отказы бесплатны, платим только за отгруженные)", n_gem * gem))
print(f"\n{'фаза':7s}{'что покупается':78s}{'смета':>9s}")
tot = 0.0
for tag, what, cost in rows:
tot += cost
print(f"{tag:7s}{what[:78]:78s}{cost:9.3f}")
print(f"{'ИТОГО':7s}{'':78s}{tot:9.3f} при потолке ${PACK_CEILING:.2f}")
free = PACK_CEILING - tot
print(f"{'резерв':7s}{'':78s}{free:9.3f}")
if free < 0:
print("\nНЕ ВЛЕЗАЕТ. Резать ось или кандидата решением сессии ЗАПРЕЩЕНО "
"(промт, анти-лень) —\n это СТОП и вопрос владельцу.")
elif free < 0.30:
print("\n⚠ Резерв меньше 8% пакета. Ре-гены эхо-мины и ретраи в паках 22/23 съедали "
"до +16%\n сверх сметы фазы — при таком резерве первый же эхо-ре-ген упрётся "
"в потолок.")
print(f"\n⚠ ДОЛЯ ОДНОЙ МОДЕЛИ: Д1 = ${n_gem * gem:.2f} = "
f"{n_gem * gem / PACK_CEILING * 100:.0f}% всего пакета фазы.")
print(f" Цена клетки ${gem:.5f} против ${edit:.5f} у боевого редактора — это "
f"${gem * 1500:.0f} за книгу\n при ${edit * 1500:.0f} у боевого. Продуктовым выбором "
"модель быть не может ни при каком исходе;\n ценность замера — закрыть гипотезу "
"эксп-04 «аутлаер прозы», не более.")
print(f"\n Сколько единиц Д1 влезает при остальном плане: "
f"{int((PACK_CEILING - (tot - n_gem * gem) - 0.35) / gem)} "
"(с резервом $0.35 на ре-гены).")
return 0
if __name__ == "__main__":
sys.exit(main())

197
eval/dovodka/power.py Normal file
View file

@ -0,0 +1,197 @@
#!/usr/bin/env python3
"""МОЩНОСТЬ СУДЕЙСКИХ ОСЕЙ ФАЗЫ Д — считается ДО покупок. $0.
Зачем. Эксп-22 купил английскую ось на n=6 и лишь ПОСЛЕ денег обнаружил, что при шести единицах
минимально достижимое p знакового теста = 2 = 0.031, а после Холма по шести контрастам 0.19,
то есть значимости не могло быть НИ ПРИ КАКОМ результате. Ось была недомощна ПО ПОСТРОЕНИЮ.
Норма фазы Д: у каждой оси напечатан MDE при плановом n, и ось, чей MDE не даёт несущего вывода,
объявляется описательной ЗАРАНЕЕ.
Два ограничения считаются раздельно, потому что упираются они в разное:
* ПОТОЛОК ЗНАЧИМОСТИ минимально достижимое p точного знакового теста при n наблюдениях с
поправкой Холма по объявленному семейству. Если он 0.05, дизайн мёртв независимо от эффекта.
* MDE (минимально различимый эффект) 2.8·sd/n, то же правило, по которому риг печатает порог
различимости. Отвечает на «какой перевес прибор вообще способен развести».
sd берётся из ЗАМЕРЕННЫХ шумовых полов прошлых паков и помечается источником: своего пола у
будущего прохода ещё нет по построению. Плюс поправка ×1.23 на межсессионную компоненту §12б
эксп-23 показал, что пол, чьи половины судит ОДИН судья, занижает порог на 19%.
Запуск: eval/.venv/bin/python eval/dovodka/power.py
"""
from __future__ import annotations
import math
import statistics
import sys
# Замеренные шумовые полы (sd разности по единицам) — источник указан у каждого.
# ⚠ ЯРЛЫКИ ИСПРАВЛЕНЫ РЕВЬЮ. Первая редакция называла пол `p3` «редакторы над одной базой» —
# это неверно: `p2zh` и `p3` разрешаются в ОДНИ И ТЕ ЖЕ 32 файла (`tp2-floor-p1/p2-E-*`), то есть
# оба меряют СКВОЗНУЮ связку «черновик+редактура», и оба несут те же две клетки `finish=length`.
# Разница 2.90 против 3.53 — чисто МЕЖСЕССИОННАЯ (×1.217), и это, кстати, СВОЯ оценка той самой
# компоненты, ради которой берётся заимствованная ×1.23 из §12б. Совпадение хорошее, но своя
# оценка теперь названа, а не умолчана.
FLOORS = {
"эксп-23 tier (редакторы над ОДНОЙ базой, zh, n=16)": 1.45,
"эксп-23 border (редакторы над ОДНОЙ базой, zh, n=16)": 2.12,
"эксп-22 p2zh (СКВОЗНАЯ связка, те же 32 файла, что p3)": 2.90,
"эксп-22 p3 (ТЕ ЖЕ файлы, другая судейская популяция)": 3.53,
}
BETWEEN = 1.23 # §12б эксп-23: наивная se → se с межсессионной компонентой
# ⚠ ЦЕЛЕВОЙ ЭФФЕКТ — то, ради чего ось покупается. Без него MDE не решает НИЧЕГО: печатать
# «MDE 1.83» и не сказать, меньше он искомого или больше, — это ровно та форма, за которую
# погорела en-ось эксп-22. Цель объявляется ДО денег и обосновывается замеренным, а не желаемым.
AXES = [
# (имя, план n, контрастов, sd-прайор, ЦЕЛЬ, обоснование прайора, обоснование цели)
("Д4 edit-контур zh", 32, 3, 2.12, 1.50,
"армы правят ОДНУ базу — это структура `tier`/`border` (sd 1.45/2.12), берётся худшая",
"решение «дешёвый фиксер вместо переписа» меняется, если разрыв ≥ трети собственного "
"эффекта редактора (+4.44 в эксп-22 Ф3) — это 1.50"),
("Д3 en→ru несущая", 16, 1, 2.12, 2.00,
"своего пола на en нет НИ ОДНОГО; прайор с zh той же структуры, пол докупается в фазе",
"несущий вопрос оси — переносится ли эффект редактора: на zh контроль +3.25, "
"значимым переносом считаем половину, 2.00"),
("Д6 ja→ru разведка", 9, 3, 2.90, 2.00,
"связка end-to-end (как p2zh) — шумнее",
"та же цель, что у en; ось объявлена ОПИСАТЕЛЬНОЙ промтом заранее и остаётся ею "
"независимо от арифметики"),
("Д1 gemini добивка", 16, 1, 2.12, 2.00,
"проход над той же якорной базой, что border — его пол ближайший",
"гипотеза эксп-04 «аутлаер прозы» — про КРУПНЫЙ отрыв; мелкий отрыв её не подтверждает "
"и продуктового смысла при цене 28× не имеет. ⚠ Именно этот порог решил спор о числе "
"клеток: при n=12 (минимум промта) MDE 2.11 БОЛЬШЕ цели и ось была бы ОПИСАТЕЛЬНОЙ; "
"владелец 10.08 выбрал полные 16, и только на них ось несущая"),
]
FORCED_DESCRIPTIVE = {"Д6 ja→ru разведка"} # объявлено промтом, арифметикой не отменяется
# ⚠ ПЕРВИЧНОЕ И ВТОРИЧНОЕ СЕМЕЙСТВА — объявляются ДО покупок, и это НЕ сужение задним числом.
# Поправка Холма делится на РАЗМЕР семейства, поэтому «взять все контрасты несущими» = сделать
# все недомощными. Пре-регистрируется так: первичное семейство несёт по одному контрасту на
# КАЖДУЮ гипотезу владельца и считается с Холмом; остальные контрасты объявлены ОПИСАТЕЛЬНЫМИ,
# печатаются с числами и поправку не несут. Ровно этот приём эксп-22 применил в §0-Ф2 к осям
# «черновик как текст», и он там сработал.
PRIMARY = {
"Д4 edit-контур zh": ["A1/A0 — флаговый контур (H-2а)",
"A3/A0 — смысловой контур (H-2б, ставка владельца)",
"A6/A0 — качественный черновик + контур (H-1)"],
"Д3 en→ru несущая": ["E0/D0 — КОНТРОЛЬ: покупает ли редактор поверх черновика (H-3)"],
"Д1 gemini добивка": ["R1/R0 — аутлаер ли проза gemini (гипотеза эксп-04)"],
}
SECONDARY = {
"Д4 edit-контур zh": ["A2/A0 — однопроходка уравненного мандата (строка 153 бэклога)",
"A4/A0 — канон-фиксер ПОСЛЕ переписа: его несущая ось — покрытие "
"канона, а оно детерминировано и шума не имеет"],
"Д3 en→ru несущая": ["E1..E5/E0 — армы контуров и однопроходки",
"E6/E0 — ВТОРОЙ редактор glm-5 (H-4): на n=16 порядок жильцов "
"разрешить нельзя, ось описательная и это сказано ДО денег"],
}
# ⚠ ПОЧЕМУ ПРАЙОР 2.12, А НЕ 2.90/3.53. Оба пола эксп-22 меряют СКВОЗНУЮ связку — они несут ещё и
# шум черновика, тогда как армы Д4 правят ОДНУ замороженную базу. Структурно ближе полы эксп-23
# (`tier` 1.45, `border` 2.12, оба над одной базой), и берётся ХУДШИЙ из них. Отдельно: обе пары
# пола эксп-22 содержат по две клетки `finish=length` (§15), что разброс завышает — без затронутых
# единиц тот же пол даёт 3.10; в фазе Д такие клетки в пачку не допускаются вовсе.
# ⚠ И честно: ПРАЙОР РЕШАЕТ. При sd 3.53 MDE оси Д4 был бы 2.48 против цели 1.50, то есть ось
# стала бы описательной. Прежняя редакция писала «прайор всё равно НЕ решает» — неверно, снято
# ревью. Решает он не окончательно: СВОЙ пол прохода меряется в фазе парой, чьи половины судят
# РАЗНЫЕ сессии, и если он выйдет хуже прайора — ось пере-классифицируется ТОГДА ЖЕ, до чтения
# боевых перевесов.
# Пространство дизайна: во что упирается вывод при разных n и разных полах.
GRID_N = (12, 16, 24, 32)
GRID_SD = (1.45, 2.12, 2.90, 3.53)
def min_p(n: int, k: int) -> float:
"""Минимально достижимое двустороннее p знакового теста при n, после Холма по k контрастам."""
return min(1.0, 2.0 ** (1 - n) * k)
def _z(p: float) -> float:
"""Квантиль стандартной нормали."""
return statistics.NormalDist().inv_cdf(p)
def mde(sd: float, n: int, k: int = 1, between: bool = True) -> float:
"""Минимально различимый эффект при 80% мощности и поправке Холма по k контрастам.
КОНСТАНТА 2.8 ВЕРНА ТОЛЬКО ПРИ k=1, и первая редакция этого файла применяла её всегда.
Поймано адверсариальным ревью снизу вверх. Решающее правило пака `p` ХОЛМА < 0.05, то есть
на самый строгий контраст семейства приходится α/k; множитель становится
(z_{1α/2k} + z_{0.8}), при k=5 это 3.417 вместо 2.8. Пропущенная поправка ×1.221.36 БОЛЬШЕ
той ×1.23, которую файл старательно вносил за межсессионную компоненту, и она переворачивала
вердикт: Д4 при k=5 давала MDE 1.57 против цели 1.50, то есть ОПИСАТЕЛЬНУЮ ось, объявленную
несущей. Это буквально провал en-оси эксп-22, ради недопущения которого файл и написан.
"""
mult = _z(1 - 0.05 / (2 * max(1, k))) + _z(0.80)
return mult * sd / math.sqrt(n) * (BETWEEN if between else 1.0)
def main() -> int:
print("ЗАМЕРЕННЫЕ ПОЛЫ, из которых берутся прайоры (sd разности по единицам):")
for k, v in FLOORS.items():
print(f" {k:54s} sd {v:.2f}")
print(f"\nпоправка на межсессионную компоненту: ×{BETWEEN} (§12б эксп-23)\n")
print(f"{'ось':24s}{'n':>4s}{'k':>3s}{'sd':>6s}{'MDE':>7s}{'ЦЕЛЬ':>7s}{'потолок p':>11s}"
" СТАТУС ОСИ")
desc = 0
for name, n, k, sd, target, _w1, _w2 in AXES:
p0 = min_p(n, k)
m_true = mde(sd, n, k)
# ⚠ ДВА УСЛОВИЯ, И ОБА НЕОБХОДИМЫ. (1) значимость достижима вообще; (2) прибор способен
# развести ИСКОМЫЙ эффект. Первая редакция смотрела только на (1) — и объявляла несущей
# ось, чей MDE вдвое больше того, что она ищет. Это и есть en-ось эксп-22, слово в слово.
why = []
if p0 >= 0.05:
why.append(f"значимость недостижима (потолок p {p0:.4f})")
if m_true > target:
why.append(f"MDE {m_true:.2f} БОЛЬШЕ искомого эффекта {target:.2f}")
if name in FORCED_DESCRIPTIVE:
why.append("объявлена описательной ЗАКАЗОМ")
status = "НЕСУЩАЯ" if not why else "⛔ ОПИСАТЕЛЬНАЯ: " + " · ".join(why)
desc += bool(why)
print(f"{name:24s}{n:4d}{k:3d}{sd:6.2f}{m_true:7.2f}{target:7.2f}{p0:11.4f} {status}")
print(f"\nописательных осей: {desc} из {len(AXES)} — объявлено ДО покупок, "
"пере-классификации задним числом не будет")
print("\nСЕМЕЙСТВА, объявленные ДО покупок:")
for name in [a[0] for a in AXES]:
if name in PRIMARY:
print(f" {name} — ПЕРВИЧНОЕ (Холм по {len(PRIMARY[name])}):")
for c in PRIMARY[name]:
print(f"{c}")
if name in SECONDARY:
print(" описательные (поправку не несут, печатаются с числами):")
for c in SECONDARY[name]:
print(f" · {c}")
print("\nОБОСНОВАНИЯ (прайор шума · искомый эффект):")
for name, _n, _k, _sd, _t, w1, w2 in AXES:
print(f" {name}\n прайор: {w1}\n цель: {w2}")
print("\nПРОСТРАНСТВО ДИЗАЙНА — честный MDE (с ×1.23) при разных n и разных полах:")
print(" " + "".join(f"{f'sd {s:.2f}':>10s}" for s in GRID_SD))
for n in GRID_N:
print(f" n={n:<4d}" + "".join(f"{mde(s, n, 1):10.2f}" for s in GRID_SD))
print(" (таблица при k=1; на семейство из k контрастов множитель растёт — см. mde())")
print("\n СКОЛЬКО ЕДИНИЦ НУЖНО, чтобы ось была несущей при sd 2.12 и цели:")
for k in (1, 2, 3, 4, 5):
for tgt in (1.50, 2.00):
need = math.ceil(((_z(1 - 0.05 / (2 * k)) + _z(0.80)) * 2.12 * BETWEEN / tgt) ** 2)
print(f" k={k} цель {tgt:.2f} → n ≥ {need}")
print("\n Для калибровки — РЕАЛЬНЫЕ перевесы, уже замеренные на этом материале:")
print(" эксп-22 Ф3: R2 2.12 · R3 2.54 · R4 4.75 · КОНТРОЛЬ +4.44")
print(" эксп-23 tier: T1 0.19 · T2 +0.50 · T3 +0.19 · КОНТРОЛЬ +2.06")
print(" эксп-23 border: R2 0.81")
print(" ⇒ Различия МЕЖДУ близкими редакторами лежат в полосе 0.22.5, то есть под MDE почти\n"
" любой строки этой таблицы. Развести их прибор способен только при малом поле И\n"
" большом n. Контрасты «есть ли редактура вообще» (+2.1…+4.4) берутся легко.")
print("\n⚠ Читать так: MDE — свойство ПЛАНА, а не результата. Свой пол каждого прохода меряется\n"
" в фазе и печатается рядом; если он окажется хуже прайора, ось пере-классифицируется в\n"
" описательную ТОГДА ЖЕ, до чтения боевых перевесов, а не после.")
print("\n⚠ Сравнение с эксп-22: его en-ось шла n=6 при k=6 ⇒ потолок p "
f"{min_p(6, 6):.4f} — дизайн не мог дать значимости. Здесь такой оси нет.")
return 0
if __name__ == "__main__":
sys.exit(main())

231
eval/dovodka/promptdiff.py Normal file
View file

@ -0,0 +1,231 @@
#!/usr/bin/env python3
"""ГЕЙТ КОНСИСТЕНТНОСТИ ПАР-ПРОМПТОВ. $0. Ненулевой код = пара не годна к покупкам.
Зачем. Эксп-19 получил конфаундированный арм: en-зеркало промпта было НЕПОЛНЫМ, и разница
армов смешалась с разницей формулировок. Заказ фазы Д требует «механический гейт консистентности
с zh-промптами (одна структура, отличаются только данные языка; дифф по шаблону гейт, не
глазами)». Вот он.
ПРАВИЛО, которое гейт проверяет:
пар-промпт обязан быть побайтной копией боевого zh-промпта ВЕЗДЕ, кроме
(а) блока пар-специфики секции «Единицы и приёмы (общие для <пара>)», это ДАННЫЕ пары;
(б) HTML-комментария провенанса в шапке.
Любое расхождение вне (а)/(б) НАРУШЕНИЕ: структура промптов разошлась, и контраст между парами
перестал быть контрастом пары.
Дополнительно сверяется то, что дифф строк не ловит:
* множества плейсхолдеров `{{}}` совпадают;
* разделитель `---USER---` присутствует в обоих и на своём месте;
* последняя инструкция (что выводить) совпадает дословно;
* заголовок пар-блока называет ИМЕННО свою пару.
Запуск: eval/.venv/bin/python eval/dovodka/promptdiff.py [<пара> ] (по умолчанию en-ru)
"""
from __future__ import annotations
import difflib
import re
import sys
from pathlib import Path
REPO = Path("/home/ubuntu/projects/textmachine")
BATTLE = REPO / "backend" / "prompts" / "zh-ru"
PAIRS = REPO / "eval" / "role_topology" / "prompts"
# Пар-блок встречается в ДВУХ формах, и первая редакция гейта знала только одну — из-за этого
# `editor.md` целиком считался «разошедшимся». Форма (1): отдельная строка-заголовок с буллетами
# под ней (`translator.md`). Форма (2): один буллет-абзац (`editor.md`). Ловим обе.
PAIR_HEAD = re.compile(r"^Единицы и приёмы \(общие для ([a-z]{2}→ru)\):\s*$")
PAIR_INLINE = re.compile(r"^-\s*Единицы и приёмы \(общие для ([a-z]{2}→ru)\):")
COMMENT = re.compile(r"(?s)<!--.*?-->\s*")
# ⚠ ВТОРАЯ ЗАКОННО ПАР-СПЕЦИФИЧНАЯ СЕКЦИЯ. Она описывает ТИПОЛОГИЮ ИСХОДНОГО ЯЗЫКА (китайский
# паратаксис против английской ритмической дроби), поэтому расходиться обязана. Но именно внутри
# неё живёт мандат арма, и ровно на его неполноте погорел эксп-19 — поэтому секция не выводится
# из-под гейта целиком: у неё сверяются ЧИСЛО пунктов и мандатные оговорки (ниже).
REFLOW_HEAD = re.compile(r"^ДИСКУРС-ПЕРЕВЁРСТКА\b")
NUMBERED = re.compile(r"^(\d+)\.\s")
# Оговорки, несущие МАНДАТ (а не типологию пары): обязаны стоять в обеих редакциях дословно.
INVARIANTS = (
"Не сливай ТОЛЬКО ради длины и не теряй смысловые атомы",
"число предложений и абзацев совпадать с исходником НЕ обязано",
"Запрещено терять смысловые атомы",
"Каждую новую реплику начинай с нового абзаца через тире",
)
# ⚠ СООТВЕТСТВИЕ ФАЙЛОВ ОБЪЯВЛЯЕТСЯ, А НЕ УГАДЫВАЕТСЯ. Первая редакция подбирала zh-аналог по
# имени с фолбэком на суффикс `-eq` — и сопоставила `translator-reflow` с `translator-reflow-eq`,
# то есть с УРАВНЕННЫМ вариантом мандата эксп-21, который зеркалом не является вовсе. Гейт,
# который сам придумывает, что с чем сравнивать, доказывает не то, что от него хотят.
MAP = {
# en-файл → (zh-оригинал, комментарий провенанса)
"translator": ("backend/prompts/zh-ru/translator.md", "боевой промпт переводчика"),
"editor": ("backend/prompts/zh-ru/editor.md", "боевой промпт редактора"),
"translator-reflow": (None, "боевого zh-аналога НЕТ: это вариант полигона, "
"зеркалом боевого промпта не является"),
}
# ⚠ ТРЕТИЙ ЗАКОННО ПАР-СПЕЦИФИЧНЫЙ КЛАСС: примеры. Они обязаны быть НА ИСХОДНОМ ЯЗЫКЕ пары,
# иначе редактор увидит образец не того языка, который правит.
EXAMPLE = re.compile(r"^\[(narrative|dialogue)\b")
# ⚠ ОБЪЯВЛЕННЫЕ РАСХОЖДЕНИЯ. Гейт зелёный ТОЛЬКО на них: любое новое расхождение роняет пару.
# Каждая строка разобрана глазами, причина записана. Так «мирится» дрейф, который иначе накопится
# молча, — и ровно этого не хватило эксп-19.
ALLOWED = {
("en-ru", "editor", "- Сверяй смысл черновика"):
"пример кальки ЗАМЕНЁН своим английским (`he raised an eyebrow` → «поднял бровь» вместо "
"«вскинул брови») — образец обязан быть на исходном языке пары. ⚠ Вставлен в фазе Д: "
"приёмка другого семейства заметила, что НЕСУЩАЯ ось en шла с зеркалом БЕДНЕЕ, чем "
"разведочная ja, и гейт это благословлял",
("en-ru", "editor", "- Вёрстка: собирай повествование"):
"zh добавляет «НЕ копируя построчную разбивку исходника (в оригинале часто одно "
"предложение — одна строка)» — свойство китайской вебновеллы; английская проза так не "
"набирается. Остаток строки совпадает дословно",
("ja-ru", "editor", "- Сверяй смысл черновика"):
"пример кальки ЗАМЕНЁН своим японским (よろしくお願いします как «прошу хорошего "
"обращения») — образец обязан быть на исходном языке пары",
("ja-ru", "editor", "- Вёрстка: собирай повествование"):
"оговорка про построчный набор СОХРАНЕНА и переформулирована под ja: веб-новелла "
"syosetu набирается построчно так же, как китайская",
}
BAD = 0
def allowed(pair: str, name: str, line: str) -> str:
for (p, f, pref), why in ALLOWED.items():
if p == pair and f == name and line.startswith(pref):
return why
return ""
def ck(name: str, ok: bool, detail: str = "") -> None:
global BAD # noqa: PLW0603
BAD += not ok
print(f" [{'OK ' if ok else 'ПРОВАЛ'}] {name}" + (f" {detail}" if detail else ""))
def strip(t: str) -> str:
return COMMENT.sub("", t)
def blocks(lines: list[str]) -> tuple[list[str], list[str], list[str], str]:
"""(строки вне пар-секций, строки «Единицы и приёмы», строки ДИСКУРС-секции, объявленная пара)."""
out, pair_lines, reflow, pair, inside, in_ref = [], [], [], "", False, False
for ln in lines:
if REFLOW_HEAD.match(ln):
in_ref, inside = True, False
reflow.append(ln)
continue
if EXAMPLE.match(ln):
reflow.append(ln)
in_ref = False
continue
if in_ref:
if ln.strip() and not NUMBERED.match(ln):
in_ref = False # секция кончилась
else:
reflow.append(ln)
continue
mi = PAIR_INLINE.match(ln)
if mi: # форма (2): один буллет
pair = pair or mi.group(1)
pair_lines.append(ln)
inside = False
continue
m = PAIR_HEAD.match(ln)
if m: # форма (1): заголовок + буллеты
inside, pair = True, m.group(1)
pair_lines.append(ln)
continue
if inside:
if ln.strip() and not ln.startswith("-"):
inside = False # блок кончился
else:
pair_lines.append(ln)
continue
out.append(ln)
return out, pair_lines, reflow, pair
def compare(name: str, zh_p: Path, en_p: Path, want_pair: str, pair: str) -> None:
print(f"\n=== {name}: {zh_p.relative_to(REPO)}{en_p.relative_to(REPO)}")
if not zh_p.exists() or not en_p.exists():
ck("оба файла существуют", False, f"нет {'zh' if not zh_p.exists() else 'en'}")
return
zh, en = strip(zh_p.read_text(encoding="utf-8")), strip(en_p.read_text(encoding="utf-8"))
zl, el = zh.splitlines(), en.splitlines()
zo, zb, zr, zp = blocks(zl)
eo, eb, er, ep = blocks(el)
ck("пар-блок объявлен в обоих", bool(zp) and bool(ep), f"zh «{zp}» · пара «{ep}»")
ck(f"пар-блок называет свою пару ({want_pair})", ep == want_pair, ep or "не найден")
# ГЛАВНАЯ ПРОВЕРКА: всё вне пар-блока обязано совпадать построчно
diff = [d for d in difflib.unified_diff(zo, eo, "zh-ru", want_pair, lineterm="", n=0)
if d[:1] in "+-" and not d.startswith(("---", "+++"))]
undecl = [d for d in diff if not allowed(pair, name, d[1:])]
ck("вне пар-секций НЕОБЪЯВЛЕННЫХ расхождений НЕТ", not undecl,
f"необъявленных строк: {len(undecl)} (всего разошлось {len(diff)})")
for d in undecl[:12]:
print(f" {d[:150]}")
if len(undecl) > 12:
print(f" … ещё {len(undecl) - 12}")
seen = {allowed(pair, name, d[1:]) for d in diff if allowed(pair, name, d[1:])}
for why in sorted(seen):
print(f" · объявленное расхождение: {why}")
zph, eph = set(re.findall(r"\{\{(\w+)\}\}", zh)), set(re.findall(r"\{\{(\w+)\}\}", en))
ck("множества плейсхолдеров совпадают", zph == eph,
f"только в zh: {sorted(zph - eph)} · только в паре: {sorted(eph - zph)}"
if zph != eph else f"{len(zph)} шт.")
ck("разделитель ---USER--- в обоих", ("---USER---" in zh) == ("---USER---" in en) is True,
f"zh {'---USER---' in zh} · пара {'---USER---' in en}")
if "---USER---" in zh and "---USER---" in en:
ck("хвост после ---USER--- совпадает",
zh.split("---USER---", 1)[1].strip() == en.split("---USER---", 1)[1].strip())
# пар-блок ОБЯЗАН отличаться — иначе пара не адаптирована вовсе
def _anon(ls: list[str]) -> list[str]:
return [re.sub(r"[a-z]{2}→ru", "<пара>", x) for x in ls if x.strip()]
ck("пар-блок содержательно ОТЛИЧАЕТСЯ (иначе пара не адаптирована)",
_anon(zb) != _anon(eb), f"строк в блоке: zh {len(zb)} · пара {len(eb)}")
# ── ДИСКУРС-секция: расходиться обязана, но НЕ терять мандат ───────────────────────────────
if zr or er:
zn = [NUMBERED.match(x).group(1) for x in zr if NUMBERED.match(x)]
en_n = [NUMBERED.match(x).group(1) for x in er if NUMBERED.match(x)]
ck("ДИСКУРС-секция есть в обоих", bool(zr) and bool(er),
f"zh {len(zr)} строк · пара {len(er)}")
ck("число пунктов ДИСКУРС-секции совпадает", zn == en_n,
f"zh {zn} · пара {en_n}")
zt, et = "\n".join(zr), "\n".join(er)
miss = [inv for inv in INVARIANTS if (inv in zt) and (inv not in et)]
ck("мандатные оговорки не потеряны в паре", not miss,
"потеряно: " + " · ".join(f"«{x}»" for x in miss) if miss else f"{len(INVARIANTS)} шт.")
extra = [inv for inv in INVARIANTS if (inv in et) and (inv not in zt)]
if extra:
print(f" ⚠ в паре есть оговорка, которой нет в zh: {extra}"
"мандат НЕ уравнен, но в обратную сторону")
def main() -> int:
pairs = [a for a in sys.argv[1:] if not a.startswith("-")] or ["en-ru"]
for p in pairs:
want = f"{p.split('-')[0]}→ru"
print(f"\n{'' * 78}\nПАРА {p}")
d = PAIRS / p
if not d.exists():
ck(f"пар-пакет {p} существует", False, str(d))
continue
for f in sorted(d.glob("*.md")):
if f.stem not in MAP:
ck(f"{f.stem}: соответствие zh-оригиналу ОБЪЯВЛЕНО", False,
"файла нет в MAP — гейт не угадывает, с чем сравнивать")
continue
rel, why = MAP[f.stem]
if rel is None:
print(f"\n=== {f.stem}: сравнение НЕ ПРОВОДИТСЯ — {why}")
continue
compare(f.stem, REPO / rel, f, want, p)
print(f"\n{'ПАР-ПРОМПТЫ КОНСИСТЕНТНЫ' if not BAD else f'НАРУШЕНИЙ: {BAD} — покупки по этой паре ЗАПРЕЩЕНЫ'}")
return BAD
if __name__ == "__main__":
sys.exit(1 if main() else 0)

View file

@ -0,0 +1,115 @@
# Реестр требований фазы Д — сверка с буквой заказа
Источник: `docs/POLYGON_EXP2223_REDO_SESSION_PROMPT.md`, прочитан ПОСТРОЧНО, включая придаточные.
Так заведено потому, что все шесть пропусков эксп-22 стояли не в нумерованных блоках, а в
придаточных предложениях, и «критик полноты», писанный той же головой, их не увидел.
**Этот файл обязан быть закоммичен ДО покупок.** `eval/conformance.py --final` отказывается
сверять незакоммиченный реестр: гейт против таблицы, которую можно дописать под результат,
ничего не доказывает — так эксп-23 получил зелёную сверку против реестра из scratchpad.
Проверка:
```
eval/.venv/bin/python eval/conformance.py eval/dovodka/requirements.md --plan # по ходу
eval/.venv/bin/python eval/conformance.py eval/dovodka/requirements.md --final # перед сдачей
```
Колонка «улика»: `$ команда` — обязана вернуть код 0; `путь` — артефакт обязан существовать и быть
непустым; `—` — улики ещё нет (в режиме `--plan` законно, в `--final` = провал).
⚠ Улика вида «греп собственного отчёта» доказывает, что НАПЕЧАТАНО, а не что СДЕЛАНО. Гейт такие
улики считает отдельно и печатает их долю; здесь они помечены в тексте требования словом «печать».
<!-- ТРЕБОВАНИЯ -->
| ID | требование (цитата или сжатие промта) | улика |
|---|---|---|
| **РАМКА** | | |
| R1 | Отчёт фазы — секции Д0Д8 в `23-editor-tier.md`, продолжение того же документа | — |
| R2 | Сводная таблица «пробел → чем закрыт → носитель-артефакт» обязательна | — |
| R3 | Пакетный потолок фазы сверен СЛОВОМ владельца при запуске | `$ grep -q "САНКЦИЯ ВЛАДЕЛЬЦА .4.00" eval/dovodka/plan.py` |
| R4 | Фазовые потолки разложены в пре-реге Д0 | — |
| R5 | Касса: фриз-гейт, атомарный замок, два пути счёта, проекционные гарды | — |
| R6 | Стоп-правило: не влезает → СТОП и пинг ДО покупок | `$ eval/.venv/bin/python eval/dovodka/plan.py` |
| R7 | Кап агент-сессий 80; запись ДО запуска суб-агента; селфтест до первой сессии | `$ eval/.venv/bin/python eval/dovodka/runs.py --selftest` |
| R8 | Гипотезы H-1, H-2а, H-2б, H-3, H-4 внесены в пре-рег ДОСЛОВНО | — |
| **Д1 — gemini** | | |
| R9 | Полная редакторская панель: 16 клеток поверх якорной базы эксп-22 | — |
| R10 | Ретрай-харнесс: экспоненциальный бэкофф на 5xx, окно ≥24 ч, поштучно, лимит цены на клетку | — |
| R11 | Журнал КАЖДОЙ попытки (время · код ответа · стоимость) файлом в сырьё | — |
| R12 | Вердикт «модель не отдаёт» — только с журналом за ≥24 ч + снимком вендор-статуса | — |
| R13 | Собрано ≥12/16 → судейство абс-ригом со всеми контролями | — |
| R14 | Потолок клетки считается по `total_tokens` (скрытая тарификация ×4.5) | — |
| **Д2 — внешняя подпись `tier`** | | |
| R15 | Задания `aj-tier-tasks` отданы внешнему судье вне Claude руками владельца | `$ test -s /home/ubuntu/books/editor-tier/sol-tier/ИНСТРУКЦИЯ.md` |
| R16 | Закрытие = НЕПУСТОЙ каталог ответов + пере-счёт скриптом класса `solscore` | `$ ls /home/ubuntu/books/editor-tier/sol-tier/*.txt` |
| **Д3 — en→ru несущая ось** | | |
| R17 | Провенанс каждого en-промпта объявлен в отчёте (печать) | — |
| R18 | Механический гейт консистентности en-промптов с zh: дифф по шаблону, не глазами | `$ eval/.venv/bin/python eval/dovodka/promptdiff.py en-ru` |
| R19 | Выверка пар-промптов против БОЕВОГО рендера | — |
| R20 | Фриз пар-промптов ДО покупок | — |
| R21 | en-банк: майнинг + терминолог по методике эксп-22, `signed:false` | — |
| R22 | Банк-закон D39.104 на ВСЕХ армах — прошлое отступление (72 клетки) не повторять | — |
| R23 | Материал: Кристофф, добор до 16 единиц методикой эксп-21/22, fr-страты сохранены | — |
| R24 | Проба контаминации гоняется ЖИЛЬЦАМИ, а не удобной дешёвой моделью | — |
| R25 | Материал непригоден по пробе → СТОП и пинг; замену без слова владельца не искать | — |
| R26 | Армы: боевая связка · dspro-однопроходка УРАВНЕННОГО мандата (строка 153) · черновик + ОБА edit-контура · контроль редактора E0/D0 | — |
| R27 | Судейство по нормам рига, Холм по объявленному семейству | — |
| **Д4 — edit-контур zh** | | |
| R28 | Якорные единицы эксп-22 переиспользуются, $0 за старые армы | — |
| R29 | Армы A0A4 обязательны; **A3 (смысловой критик → фиксер) — ставка владельца, обязателен**; A5 — опция при остатке | — |
| R30 | Позиция контура: A1/A3 «ВМЕСТО редактора», A4 «ПОСЛЕ»; «до редактора» не мерится | — |
| R31 | Канон-гейт — детерминированная сверка покрытия банка после КАЖДОГО арма | — |
| R32 | Ответ по каждому арму печатается ТРЕМЯ осями: судья · канон-покрытие · цена | — |
| R33 | Несущий вердикт фазы на zh — внешняя подпись судьёй вне Claude | — |
| **Д5 — канон-вскрытие** | | |
| R34 | КАЖДОЕ место потери покрытия у R0 классифицировано (исчез / парафраз / другой перевод), таблица по терминам и местам | `$ eval/.venv/bin/python eval/dovodka/canon.py` |
| R35 | Результат вскрытия — регрессионный набор для канон-фиксера | `/home/ubuntu/books/dovodka/canon-dissect.json` |
| **Д6 — ja→ru разведка** | | |
| R36 | Материал `isekai_majutsushi_jp`, проба контаминации жильцами | `$ eval/.venv/bin/python eval/dovodka/material_ja.py; test $? -le 1` |
| R37 | Непригоден → СТОП и пинг; замену без слова владельца не искать | `$ eval/.venv/bin/python eval/dovodka/material_ja.py \| grep -q "СТОП\|OK "` |
| R38 | ja-промпты — те же гейты провенанса и консистентности, что Д3 | `$ eval/.venv/bin/python eval/dovodka/promptdiff.py ja-ru` |
| R39 | 810 единиц: связка · dspro-однопроходка · контроль редактора | — |
| R40 | Печатается сравнение ОТНОСИТЕЛЬНОГО порядка жильцов между парами zh/en/ja | — |
| R41 | Статус оси РАЗВЕДКА объявлен В ПРЕ-РЕГЕ, а не постфактум | — |
| **Д7 — самооценка** | | |
| R42 | Жильцам обеих ролей по трём книгам (zh/en/ja) вопрос о самооценке; ответы персистятся | — |
| R43 | Сверка самооценки с фактическими результатами пар; решений на ней не стоит | — |
| **Д8 — поправки тел по приёмке №16** | | |
| R44 | §15 эксп-22 переписан: 5 из 6 клеток — эхо, пойманное гейтом; гейт длины сужен до `finish=length` | `$ grep -q "ИНТЕРПРЕТАЦИЯ ПЕРЕПИСАНА ФАЗОЙ Д" docs/experiments/22-tenant-panel.md` |
| R45 | §13а эксп-22: банк-закон на оси en НЕ исполнен | `$ grep -q "на zh исполнено, на en НЕТ" docs/experiments/22-tenant-panel.md` |
| R46 | Sol-статусы §10/§11/§14 эксп-22 приведены к §16; оборванная фраза дописана | `$ grep -q "АРБИТРАЖ НЕ СОСТОЯЛСЯ; ПАКЕТ (б)" docs/experiments/22-tenant-panel.md` |
| R47 | Счёт агент-сессий эксп-23 = 58, не 50 (§6, §14 п.9) + пинг в PROGRESS | `$ grep -q "Агент-запусков: 58" docs/experiments/23-editor-tier.md` |
| R48 | Противоречие §9/§13 эксп-23 против §12в о внешнем контуре снято | `$ grep -q "Внешний контур есть у .border. и НЕТ у .tier." docs/experiments/23-editor-tier.md` |
| R49 | Наклон `border` пере-снят (+0.708) и сторожится гейтом | `$ eval/.venv/bin/python eval/editor_tier/verify23.py` |
| R50 | Таблица §0 эксп-23: строка R2 помечена как число АННУЛИРОВАННОГО run2 | `$ grep -q "run2, ТОЖЕ АННУЛИРОВАН" docs/experiments/23-editor-tier.md` |
| R51 | Пере-штамповка `aj-border*` объявлена (копии `replication-runA`, mtime 23:31:54) | `$ grep -q "ПРОВЕНАНС КАТАЛОГОВ СУДЕЙСТВА .border. УТРАЧЕН" docs/experiments/23-editor-tier.md` |
| R52 | Голоса `replication-runB` несли judge-имена прогона A: `ingest` починен, объявлено | `$ eval/.venv/bin/python eval/editor_tier/judge.py --run replication-runB border --score` |
| R53 | Нумерация: два «п.7» в §8 эксп-23 и порядок §9 эксп-22 исправлены, ссылки поправлены | `$ grep -q "три ссылки в §13а и §14" docs/experiments/22-tenant-panel.md` |
| R54 | `eval/README.md`: `editor_tier/` добавлен в карту действующих харнессов | `$ grep -q "editor_tier/. . ..exp-23" eval/README.md` |
| R55 | Статус-баннеры на отчётах экспов, где их нет | `$ test $(grep -l "баннер фазы Д" docs/experiments/*.md \| wc -l) -ge 7` |
| R56 | Шапка эндпоинтов `00-provider-quirks.md` актуализирована живым листингом | `$ grep -q "ЖИВОЙ ЛИСТИНГ ./models. ПЕРЕ-СНЯТ 2026-08-10" docs/experiments/00-provider-quirks.md` |
| **НОРМЫ РИГА** | | |
| R57 | Декой в каждой судейской пачке; сессия без пойманного декоя аннулируется целиком | — |
| R58 | Шумовой пол — парой, чьи половины судят РАЗНЫЕ сессии (порог был занижен на 19%) | — |
| R59 | Все армы единицы — в одном пакете одной сессии | — |
| R60 | Идентичность судей персистится ДО запуска; половиной пола не брать боевую клетку | `$ eval/.venv/bin/python eval/dovodka/runs.py --selftest` |
| R61 | Позиционный наклон замерен, вычтен, сторожится гейтом; донор декоя уравнен | `$ eval/.venv/bin/python eval/editor_tier/verify23.py` |
| R62 | Судье запрещено сравнивать варианты между собой И читать их рядом | — |
| R63 | Замок кассы снимает только поставивший его процесс (проверка живости PID) | — |
| R64 | Реестр требований фазы — В GIT до покупок; conformance валиден только против закоммиченного | `$ eval/.venv/bin/python -c "import sys;sys.path.insert(0,'eval');import conformance as c;ok,why=c.frozen(c.Path('eval/dovodka/requirements.md'));print(why);sys.exit(0 if ok else 1)"` |
| R65 | MDE каждой судейской оси — в пре-рег ДО покупок; недомощная ось объявляется описательной ЗАРАНЕЕ | `$ eval/.venv/bin/python eval/dovodka/power.py` |
| R66 | На старте фазы пере-снят прайс DeepSeek; смена прайса → пере-смета и пинг ДО покупок | `$ grep -q "3в. ВАХТА D39.92 ИСПОЛНЕНА ПОВТОРНО 2026-08-10" docs/experiments/00-provider-quirks.md` |
| **АНТИ-ЛЕНЬ** | | |
| R67 | Не собралось с первой попытки → ДОБИВАТЬСЯ; отказ принимается только с журналом попыток | — |
| R68 | Панель, ось или кандидата НЕ резать решением сессии — СТОП и пинг владельцу | — |
| R69 | Обязательство с адресатом вне зоны закрывается ТОЛЬКО изменением файла вне зоны | `$ ls /home/ubuntu/books/editor-tier/sol-tier/*.txt` |
| R70 | Детекторы и пороги после взгляда на вердикты не менять; девиация = СТОП и пинг В МОМЕНТ | — |
| R71 | Правка рига чужого пака — отдельным коммитом + пере-снятие его гейтов | `$ eval/.venv/bin/python eval/tenant_panel/verify22.py` |
| R72 | Приёмка адверсариальная author≠reviewer; опровергатель ДРУГОГО модельного семейства ОБЯЗАТЕЛЕН | — |
| R73 | Финал — построчный аудит закрытия заказа | — |
| **ОТЧЁТ И СДАЧА** | | |
| R74 | Сверка H-1…H-4 с фактом отдельной таблицей, ВКЛЮЧАЯ опровержения | — |
| R75 | CONFIRM/DENY владельцу — только с артефактом-носителем | — |
| R76 | Деньги: итог по фазам ДВУМЯ путями счёта | — |
| R77 | Пинг в `docs/PROGRESS.md` секция «Полигон» при закрытии | — |

285
eval/dovodka/runs.py Normal file
View file

@ -0,0 +1,285 @@
#!/usr/bin/env python3
"""ЖУРНАЛ АГЕНТ-СЕССИЙ ФАЗЫ Д. $0. Кап 80 (заказ владельца 10.08).
ЗАЧЕМ ОТДЕЛЬНЫЙ МОДУЛЬ, А НЕ СТРОКА В `judge.py`. В паке 23 счётчик существовал и не работал:
`log_run` звался из `--ingest`, то есть ПОСЛЕ того, как суб-агент отработал. На диске осталось
20 записей против 58 фактических сессий; при капе 80 такой счётчик пропустил бы ещё 60 сторожить
кап можно ТОЛЬКО записью, сделанной ДО запуска. Здесь это и есть контракт: `claim()` пишет запись и возвращает имя судьи,
которое передаётся суб-агенту; не позвал `claim` не получил имени, и разбор проставит `?`.
ВТОРОЙ ДЕФЕКТ, КОТОРЫЙ ЧИНИТ ЭТОТ ЖЕ ФАЙЛ. Карта «токен судья» в паке 23 лежала одна на
ПРОХОД, а репликация судила ТЕ ЖЕ токены вторым жребием судей и все её голоса получили имена
судей боевого прогона. Здесь запись помнит ПРОГОН и ВРЕМЯ, и `judge_of` разводит два жребия по
одним и тем же токенам ОКНОМ сессии `[at, done_at]`.
ТРЕТИЙ, И САМЫЙ ДОРОГОЙ. Атрибуция по времени работает, только пока сессии не перекрываются.
Пак 23 отдал пять единиц на пере-суживание ДВАЖДЫ, не дождавшись первого круга: оба круга писали
в одни файлы, `--ingest` прошёл между ними, и голоса разошлись с сырьём на 5 единицах из 32.
Поэтому `claim` ОТКАЗЫВАЕТ, если сессия с пересекающимися токенами ещё не закрыта `done`.
Первая редакция называла это «замком, который невозможно обойти механически» неверно, и
поймано приёмкой другого модельного семейства: `claim` делал read-modify-write журнала БЕЗ
файлового лока, так что два параллельных вызова оба прочли бы леджер до записи друг друга,
оба прошли бы проверку пересечения и получили один и тот же номер. Тот же класс, что «неатомарная
проверка файла» в кассе эксп-22, стоившая ему границы пере-оплаты $0.846103. Теперь журнал
берётся под атомарный `O_CREAT|O_EXCL`-замок, как `money.py`, и проверка пересечения идёт ВНУТРИ
критической секции.
Команды:
runs.py --claim <прогон> <проход> <ток> [<ток> ] [--note ""] запись ДО запуска суб-агента
runs.py --done <n> закрыть сессию ПОСЛЕ ответа
runs.py --report сколько израсходовано
runs.py --selftest обязателен ДО первой сессии
"""
from __future__ import annotations
import contextlib
import datetime as dt
import json
import os
import sys
import time
from pathlib import Path
OUT = Path.home() / "books" / "dovodka"
LEDGER = OUT / "agent-runs.json"
CAP = 80
FAR_FUTURE = 1e18
LOCK_TRIES = 600 # ×0.1 с — минута ожидания живого замка
def _now() -> str:
return dt.datetime.now(dt.timezone.utc).isoformat(timespec="microseconds")
def _ts(iso: str | None) -> float:
return dt.datetime.fromisoformat(iso).timestamp() if iso else FAR_FUTURE
def used() -> list[dict]:
return json.loads(LEDGER.read_text(encoding="utf-8")) if LEDGER.exists() else []
def _write(rs: list[dict]) -> None:
"""⚠ Запись АТОМАРНА: `write_text` усекает файл и пишет заново, и падение между этими двумя
шагами оставляло журнал нечитаемым (поймано ревью исполнением: 2 прогона из 20). Пишем во
временный файл и подменяем `os.replace` она атомарна в пределах ФС."""
OUT.mkdir(parents=True, exist_ok=True)
tmp = LEDGER.with_suffix(".tmp")
tmp.write_text(json.dumps(rs, ensure_ascii=False, indent=1), encoding="utf-8")
os.replace(tmp, LEDGER)
@contextlib.contextmanager
def _locked():
"""Атомарный замок журнала. `O_CREAT|O_EXCL` — та же механика, что у кассы эксп-22.
Замок снимает ТОЛЬКО поставивший его процесс: в файл пишется PID, и протухшим замок
считается лишь тогда, когда процесса с этим PID больше нет (норма рига, промт фазы Д).
"""
OUT.mkdir(parents=True, exist_ok=True)
lock = LEDGER.with_suffix(".lock")
for _ in range(LOCK_TRIES):
try:
fd = os.open(str(lock), os.O_CREAT | os.O_EXCL | os.O_WRONLY)
os.write(fd, str(os.getpid()).encode())
os.close(fd)
break
except FileExistsError:
try:
pid = int(lock.read_text() or 0)
os.kill(pid, 0) # процесс жив — ждём
except (ValueError, OSError):
lock.unlink(missing_ok=True) # ставивший умер — снимаем
continue
time.sleep(0.1)
else:
raise SystemExit("⛔ журнал агент-сессий заперт дольше минуты — разберись, кто держит")
try:
yield
finally:
with contextlib.suppress(OSError):
if lock.exists() and lock.read_text().strip() == str(os.getpid()):
lock.unlink()
def claim(run: str, pass_: str, tokens: list[str], note: str = "") -> dict:
"""Занять агент-сессию ДО запуска суб-агента. Возвращает запись с именем судьи.
Кап проверяется ПЕРЕД записью, а не после: иначе 81-я сессия успевала бы отработать.
"""
with _locked():
return _claim_locked(run, pass_, tokens, note)
def _claim_locked(run: str, pass_: str, tokens: list[str], note: str) -> dict:
rs = used()
live = [r for r in rs if r.get("done_at") is None and set(r["tokens"]) & set(tokens)]
if live:
raise SystemExit(
"⛔ ГОНКА СЕССИЙ: токены уже отданы незакрытой сессии "
f"#{live[0]['n']} ({live[0]['judge']}, {sorted(set(live[0]['tokens']) & set(tokens))}).\n"
" Дождись её ответов и закрой `--done`, либо возьми другие токены. Ровно на этом\n"
" пак 23 потерял соответствие голосов и сырья на 5 единицах из 32.")
n = len(rs) + 1
if n > CAP:
raise SystemExit(f"⛔ КАП АГЕНТ-СЕССИЙ ФАЗЫ Д ПРОБИТ: {n} > {CAP} — СТОП и пинг владельцу")
rec = dict(n=n, at=_now(), done_at=None, run=run, pass_=pass_, judge=f"д-{n:02d}",
tokens=list(tokens), note=note)
rs.append(rec)
_write(rs)
return rec
def done(n: int) -> dict:
"""⚠ ТОЖЕ ПОД ЗАМКОМ. Первая редакция брала замок только в `claim`, а `done` делала свой
read-modify-write мимо него и параллельные `claim`+`done` теряли запись claim в 19 прогонах
из 20: суб-агент запущен, имя судьи выдано, а в журнале его нет. Кап при этом недосчитывает,
`judge_of` отдаёт `?`, а замок пересечения токенов пускает на них вторую сессию. Нашло ревью
исполнением; селфтест этого не видел, потому что гонял только последовательные сценарии."""
with _locked():
return _done_locked(n)
def _done_locked(n: int) -> dict:
rs = used()
hit = [r for r in rs if r["n"] == n]
if not hit:
raise SystemExit(f"⛔ сессии #{n} в журнале нет")
hit[0]["done_at"] = _now()
_write(rs)
return hit[0]
def judge_of(run: str, token: str, answer_mtime: float) -> str:
"""Кто судил ЭТОТ ответ. Сессия владеет ответом, если он записан в её окне `[at, done_at]`.
Хвост допуска: ответ, пришедший ПОСЛЕ закрытия последней накрывающей сессии, отдаётся ей же
закрытие ставится руками и может отстать от файловой системы на секунды. Ответ РАНЬШЕ первой
регистрации не отдаётся никому: он не мог быть ею произведён.
"""
cand = sorted((r for r in used() if r["run"] == run and token in r["tokens"]),
key=lambda r: _ts(r["at"]))
inwin = [r for r in cand if _ts(r["at"]) <= answer_mtime <= _ts(r.get("done_at")) + 5.0]
if inwin:
return inwin[-1]["judge"]
started = [r for r in cand if _ts(r["at"]) <= answer_mtime]
return started[-1]["judge"] if started else "?"
def report() -> None:
rs = used()
print(f"агент-сессий израсходовано {len(rs)} из {CAP}")
live = [r for r in rs if r.get("done_at") is None]
per: dict[str, int] = {}
for r in rs:
per[r["run"]] = per.get(r["run"], 0) + 1
for k, v in sorted(per.items()):
print(f" {k:28s} {v}")
if live:
print(f"⚠ НЕЗАКРЫТЫХ сессий {len(live)}: " + " ".join(f"#{r['n']}" for r in live))
def selftest() -> int:
"""Проверка ИСПОЛНЕНИЕМ, а не чтением: журнал подменяется временным, гоняются сценарии."""
global LEDGER, CAP # noqa: PLW0603
keep_l, keep_c = LEDGER, CAP
OUT.mkdir(parents=True, exist_ok=True)
LEDGER = OUT / "SELFTEST-agent-runs.json"
LEDGER.unlink(missing_ok=True)
bad = 0
def ck(name: str, ok: bool, detail: str = "") -> None:
nonlocal bad
bad += not ok
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {name}" + (f" {detail}" if detail else ""))
try:
# 1. запись появляется ДО того, как что-либо отработало
a = claim("проверка", "px", ["t1", "t2"], note="первый жребий")
ck("claim пишет запись сразу", LEDGER.exists() and len(used()) == 1, f"n={a['n']}")
ck("судья именован", a["judge"] == "д-01", a["judge"])
# 2. ЗАМОК ГОНКИ: те же токены нельзя отдать, пока сессия не закрыта
try:
claim("проверка", "px", ["t2"])
ck("гонка сессий по одному токену запрещена", False, "пропустил вторую сессию")
except SystemExit:
ck("гонка сессий по одному токену запрещена", True)
ck("отказавшая сессия НЕ записана", len(used()) == 1, f"{len(used())} записей")
# ⚠ ЗАМОК ПРОВЕРЯЕТСЯ ИСПОЛНЕНИЕМ, а не чтением. Прежний селфтест гонял только
# последовательные сценарии и дыру read-modify-write не видел — её нашла приёмка
# другого модельного семейства. Здесь замок ставится ЧУЖИМ живым PID и claim обязан
# ждать, а не пройти мимо.
global LOCK_TRIES # noqa: PLW0603
keep_tries, LOCK_TRIES = LOCK_TRIES, 5 # не ждать минуту в селфтесте
lock = LEDGER.with_suffix(".lock")
lock.write_text(str(os.getpid())) # живой PID = замок держится
t0 = time.time()
try:
claim("проверка", "px", ["t5"])
waited = time.time() - t0
ck("claim ЖДЁТ живой замок, а не проходит мимо", False,
f"прошёл за {waited:.2f} с при занятом замке")
except SystemExit:
ck("claim ЖДЁТ живой замок, а не проходит мимо", True, "упёрся в таймаут — верно")
finally:
lock.unlink(missing_ok=True)
LOCK_TRIES = keep_tries
lock.write_text("999999999") # мёртвый PID = замок протух
n_before = len(used())
claim("проверка", "px", ["t6"])
ck("протухший замок (мёртвый PID) снимается", len(used()) == n_before + 1)
ck("замок за собой убран", not lock.exists())
n_now = len(used())
ck("непересекающиеся токены разрешены", claim("проверка", "px", ["t7"])["n"] == n_now + 1)
# 3. ВТОРОЙ ЖРЕБИЙ ПО ТЕМ ЖЕ ТОКЕНАМ — тот самый дефект пака 23
t_first = _ts(a["at"]) + 0.001 # ответ пришёл в окне первой
done(a["n"])
b = claim("проверка", "px", ["t1", "t2"], note="второй жребий")
t_second = _ts(b["at"]) + 0.001
done(b["n"])
ck("ответ ПЕРВОГО жребия атрибутируется первому",
judge_of("проверка", "t1", t_first) == "д-01", judge_of("проверка", "t1", t_first))
ck("ответ ВТОРОГО жребия атрибутируется второму",
judge_of("проверка", "t1", t_second) == b["judge"],
f"{judge_of('проверка', 't1', t_second)} против {b['judge']}")
ck("ответ РАНЬШЕ первой регистрации не отдаётся никому",
judge_of("проверка", "t1", _ts(a["at"]) - 10) == "?")
ck("чужой прогон не атрибутируется", judge_of("другой", "t1", t_second) == "?")
ck("незарегистрированный токен даёт '?'", judge_of("проверка", "нет", t_second) == "?")
# 4. КАП ОБЯЗАН ОТКАЗАТЬ ДО ЗАПУСКА, а не после
CAP = len(used())
try:
claim("проверка", "px", ["t9"])
ck("кап отказывает при исчерпании", False, "пропустил лишнюю сессию")
except SystemExit:
ck("кап отказывает при исчерпании", True)
ck("сверх-капная сессия НЕ записана", len(used()) == CAP, f"{len(used())} записей")
finally:
LEDGER.unlink(missing_ok=True)
LEDGER, CAP = keep_l, keep_c
print(f"\n{'ЖУРНАЛ ГОДЕН' if not bad else f'ПРОВАЛОВ: {bad}'}")
return bad
if __name__ == "__main__":
a = sys.argv[1:] or ["--report"]
if a[0] == "--selftest":
sys.exit(1 if selftest() else 0)
elif a[0] == "--done":
r = done(int(a[1]))
print(f"сессия #{r['n']} ({r['judge']}) закрыта {r['done_at']}")
elif a[0] == "--claim":
note = ""
if "--note" in a:
i = a.index("--note")
note, a = (a[i + 1] if len(a) > i + 1 else ""), a[:i]
if len(a) < 4:
raise SystemExit("нужно: --claim <прогон> <проход> <ток> [<ток> …]")
r = claim(a[1], a[2], a[3:], note=note)
print(f"сессия #{r['n']}/{CAP} · судья {r['judge']} · токенов {len(r['tokens'])}")
else:
report()

View file

@ -8,7 +8,7 @@
Жанр книги: {{genre}}. Аудитория: {{audience}}. Книга: «{{title}}».
Твой мандат — художественная редактура черновика со сверкой по исходнику:
- Сверяй смысл черновика с исходным текстом (язык «{{source_lang}}»): исправляй искажения смысла, неверно понятые реалии, кальки жестов и идиом — переводчик мог передать выражение буквально. Опирайся на исходник, но пиши по-русски, а не подстрочником.
- Сверяй смысл черновика с исходным текстом (язык «{{source_lang}}»): исправляй искажения смысла, неверно понятые реалии, кальки жестов и идиом — переводчик мог передать выражение буквально (напр. `he raised an eyebrow` как «он поднял бровь» вместо «он вскинул брови»). Опирайся на исходник, но пиши по-русски, а не подстрочником.
- Правь стиль: убирай кальки с языка «{{source_lang}}», канцелярит, лишние повторы, неестественные конструкции; пиши живым литературным русским. НО повтор, который стоит в тех же (параллельных) позициях самого исходника, — это авторский рефрен: сохраняй его, не «разнообразь» лексику там, где автор намеренно повторяется.
- Имена собственные, термины и реалии передавай СТРОГО по приведённому глоссарию (если он приложен): приводи любые расхождения к каноническим формам, склоняя по контексту; не придумывай иных вариантов их передачи.
- Вёрстка: собирай повествование в естественные русские абзацы. Реплики прямой речи начинай с нового абзаца через тире «—». Максимума длины абзаца нет.

View file

@ -0,0 +1,46 @@
<!-- ПАР-ПАКЕТ ja→ru, заведён полигоном 10.08 для фазы Д (ось Д6, третья пара продукта).
Производный от боевого backend/prompts/zh-ru/editor.md. Расхождения РОВНО те, что объявлены
гейтом eval/dovodka/promptdiff.py: блок «Единицы и приёмы», секция ДИСКУРС-ПЕРЕВЁРСТКА и
примеры (они обязаны быть на исходном языке пары). Все прочие строки перенесены дословно,
чтобы сравнение пар не смешивалось с разницей формулировок — урок эксп-19. -->
Ты — литературный редактор художественного перевода на русский язык. Тебе даны ИСХОДНЫЙ текст и его черновой перевод.
Жанр книги: {{genre}}. Аудитория: {{audience}}. Книга: «{{title}}».
Твой мандат — художественная редактура черновика со сверкой по исходнику:
- Сверяй смысл черновика с исходным текстом (язык «{{source_lang}}»): исправляй искажения смысла, неверно понятые реалии, кальки жестов и идиом — переводчик мог передать выражение буквально (напр. よろしくお願いします как «прошу хорошего обращения»). Опирайся на исходник, но пиши по-русски, а не подстрочником.
- Правь стиль: убирай кальки с языка «{{source_lang}}», канцелярит, лишние повторы, неестественные конструкции; пиши живым литературным русским. НО повтор, который стоит в тех же (параллельных) позициях самого исходника, — это авторский рефрен: сохраняй его, не «разнообразь» лексику там, где автор намеренно повторяется.
- Имена собственные, термины и реалии передавай СТРОГО по приведённому глоссарию (если он приложен): приводи любые расхождения к каноническим формам, склоняя по контексту; не придумывай иных вариантов их передачи.
- Вёрстка: собирай повествование в естественные русские абзацы, НЕ копируя построчную разбивку исходника (веб-новелла набирается построчно). Реплики прямой речи начинай с нового абзаца через тире «—». Максимума длины абзаца нет.
- Сохраняй ПОЛНОТУ черновика: не выбрасывай и не добавляй предложения; не пересочиняй сцены.
- Единицы и приёмы (общие для ja→ru): МЕРЫ приводи к привычным читателю единицам (традиционные 尺/里/坪 — метрическими либо привычными приблизительными оборотами). ЗВУКОПОДРАЖАНИЯ и ОБРАЗОПОДРАЖАНИЯ (擬音語/擬態語) передавай русским глаголом, наречием или образом, НЕ слоговой транслитерацией. ХОНОРИФИКИ и уровни вежливости веди по брифу и держи одну форму; смену вежливости передавай русским регистром. ОПУЩЕННОЕ ПОДЛЕЖАЩЕЕ восстанавливай по контексту. ТРАНСКРИПЦИЯ — по Поливанову, не по ромадзи. СТИХИ и названия классики — по смыслу, не транслитерацией. Род и форму имён и терминов сверяй по глоссарию.
- Хонорифики: {{honorifics}}. Баланс форенизация/доместикация: {{venuti}}.
Выведи ТОЛЬКО отредактированный текст перевода — без служебных преамбул, комментариев, пояснений, заметок о правках и markdown-заголовков.
ДИСКУРС-ПЕРЕВЁРСТКА (приведение японского членения и эллипсиса к русской абзацной и синтаксической норме — это КОНВЕНЦИЯ русского языка, а не вольность):
1. Молча определи смысловые ходы фрагмента: единый фокал/наблюдатель; действие и его непосредственная реакция; одна тема рассуждения; границы — смена говорящего, времени, места или фокала.
2. Один повествовательный ход оформляй ОДНИМ русским абзацем, ДАЖЕ если японский исходник разбит на однофразовые строки (веб-новелла набирается построчно). Инструмент слияния — причастные и деепричастные обороты, подчинительные союзы и связки. Не сливай ТОЛЬКО ради длины и не теряй смысловые атомы.
3. Каждую новую реплику начинай с нового абзаца через тире «—»; слова автора при той же реплике оставляй в её абзаце. Японские скобки прямой речи 「」 заменяй русским тире, 『』 — кавычками-ёлочками.
4. Разрешено объединять и делить русские предложения; число предложений и абзацев совпадать с исходником НЕ обязано. Запрещено терять смысловые атомы (участников, действия, числа, полярность, заголовки, а также идиомы и ёдзидзюкуго: их передавай смыслом, не сворачивая в общее место и не калькируя дословно).
---FEWSHOT---
Примеры требуемой ПЕРЕВЁРСТКИ (показана структура вывода, не стиль):
[narrative N:1] Исходник построчно — «扉を開けて外に出た。» / «空は灰色だった。» / «彼は息をついた。» / «道はどこまでも続いていた。» — становится ОДНИМ абзацем:
Он вышел за дверь. Небо было серым; вздохнув, он смотрел, как дорога уходит вдаль.
[dialogue 1:N] Исходник — «叔父は眉をひそめて言った。「時が経つのは早いな。座れ。」「ありがとうございます」と甥が答えた。» — становится:
Дядя нахмурился.
— Время летит. Садись, — сказал он.
— Спасибо, — ответил племянник.
[focal-shift] При смене наблюдателя/места/времени — новый абзац, даже если в исходнике это продолжение той же строки.
---USER---
Исходный текст (язык «{{source_lang}}»):
{{text}}
Черновик перевода для редактуры:
{{draft}}

View file

@ -0,0 +1,30 @@
<!-- ПАР-ПАКЕТ ja→ru, заведён полигоном 10.08 для фазы Д (ось Д6, третья пара продукта).
Производный от боевого backend/prompts/zh-ru/translator.md. Расхождения РОВНО те, что объявлены
гейтом eval/dovodka/promptdiff.py: блок «Единицы и приёмы», секция ДИСКУРС-ПЕРЕВЁРСТКА и
примеры (они обязаны быть на исходном языке пары). Все прочие строки перенесены дословно,
чтобы сравнение пар не смешивалось с разницей формулировок — урок эксп-19. -->
Ты — профессиональный литературный переводчик с языка «{{source_lang}}» на язык «{{target_lang}}».
Жанр книги: {{genre}}. Аудитория: {{audience}}. Книга: «{{title}}».
Правила перевода (translation brief):
- Хонорифики: {{honorifics}}. Транскрипция имён и реалий: {{transcription}}.
- Баланс форенизация/доместикация (0 — полная адаптация, 1 — сохранение чужого): {{venuti}}.
- Сноски: {{footnotes}}.
- Переводи ВСЕ предложения исходника: ничего не пропускай, не сокращай и не добавляй от себя.
- Вёрстка — по нормам русского языка, НЕ копируй построчную разбивку исходника: реплики прямой речи оформляй с нового абзаца через тире «—».
- Пиши живым литературным русским языком; избегай канцелярита и калек с исходного языка.
Единицы и приёмы (общие для ja→ru):
- МЕРЫ переводи в привычные читателю единицы: традиционные 尺 (сяку ≈ 30 см), 寸, 里 (ри ≈ 3.9 км), 坪, 石, 両 передавай метрическими либо привычными приблизительными оборотами, сам термин не транслитерируй; современные метрические единицы оставляй как есть.
- ЗВУКОПОДРАЖАНИЯ и ОБРАЗОПОДРАЖАНИЯ (擬音語 / 擬態語: ドキドキ, じっと, ぼんやり) — отдельный слой, и калькировать их нельзя: передавай русским глаголом, наречием или образом («сердце заколотилось», «замер», «рассеянно»), а не слоговой транслитерацией.
- ХОНОРИФИКИ и вежливые формы: суффиксы (-сан, -кун, -тян, -сама, -сэмпай) веди по правилу брифа {{honorifics}} и держи ОДНУ форму на весь текст; смену уровня вежливости (敬語 → простая форма) передавай РУССКИМ регистром — обращением на «вы»/«ты» и лексикой, а не пометками.
- ОПУЩЕННОЕ ПОДЛЕЖАЩЕЕ восстанавливай по контексту: японская фраза сплошь и рядом обходится без него, русская — нет. Не выдумывай участника, которого в сцене нет.
- ТРАНСКРИПЦИЯ имён и реалий — по системе Поливанова (си, дзи, тя, дзю), а не по английской ромадзи (shi, ji, cha, ju); одна форма имени на весь текст.
- СТИХИ и названия классики передавай ПО СМЫСЛУ (не транслитерацией), опираясь на существующие русские переводы.
- Род и форму имён собственных и терминов бери из приложенного глоссария (если он есть).
Выведи ТОЛЬКО перевод, без служебных преамбул, комментариев, пояснений и markdown-заголовков.
---USER---
Переведи следующий фрагмент:
{{text}}