diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 48928066..ead47f23 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -2,13 +2,13 @@
> **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-08-09, голова D39.126; хендофф №15→№16 — D39.124/125, докс-пак 167 исполнен — D39.126). Оркестраторов ДВА (решение владельца 07.08): этот — движок/платформа/фронт/доки; параллельный (РОЛЬЮ, без номера — счётчик один, D39.112 п.6) — приёмка полигона. Одновременно не запускаются; CURRENT-STATE ведут оба, чужие строки не трогают. Норма изоляции панелей после инцидента отката чужих файлов — D39.113, гардрейлы в CLAUDE.md.
> - **Эра №15 закрыта — семь приёмок, все ПРИНЯТЫ и залендены**; лента, коммиты и разборы — D39.109–123 (D-лог) и реестр нот `architecture/05-decisions-index.md`; снимок прежних бюллетеней этой шапки — архив-слайс `-08-02-04`.
-> - **ЖИВОЕ:** полигон эксп-22 «панель жильцов» — ЗАКРЫТ 09.08, ЖДЁТ ЛЕНДИНГА (8 CONFIRM/DENY на владельце; секция «Полигон» ниже; в дереве замечен незаявленный эксп-23 editor_tier — спросить при лендинге) · фронт: S4 ГЕЙЧЕН словом владельца об оболочке → сначала «S3.5 фикс-пак» (D39.119 п.4) · активных промтов бэкенда/платформы НЕТ — следующие по слову владельца: **эмиттер** (103 + деньги шва; сессии перед стартом читать PD-95/105/79/99/107/60/61 + D39.106 + строку 165) · **платформа**: стоп/резюм-ручки + эскроу (136).
-> - **Открыто на владельце:** прогон Sol-пакетов руками (150) · кредиты xAI (арбитр grok + судья 18+) · Приложение А контракта (148: реконструкция с баннером или заново) · продуктовое слово «остановлена: лимиты» (В-3) · К-13/Ф-31 (paused_reason один — «свой потолок» и «нет кредита» неразличимы) · В-4/Ф-30 (глава без заголовка) · фри-тир PD-104 (ноль на бете против автогранта $5 с суточным потолком) · порог 116 · мини-проба флора 44 (одобрена, промт не выдан) · подпись денежного шага 46 · лист В-3+К-6 · авто-резюм paused (вопрос платформы) · запуск S3.5/эмиттера/следующего платформенного пака.
+> - **ЖИВОЕ:** полигон эксп-22/23 — дерево ЗАЛЕНДЕНО 10.08 с ревью-шапками приёмки №16 (числа подтверждены независимым пересчётом; **выводы НЕ ратифицированы** — владелец 10.08 признал постановку неполной), **фаза Д заказана**: промт `docs/POLYGON_EXP2223_DOVODKA_SESSION_PROMPT.md`, запуск по слову владельца (санкция потолка $10.00 при запуске) · фронт: список претензий владельца ПРИШЁЛ 09.08 (24 замечания) — промт S3.5 ВЫДАН (`frontend/docs/S35_SESSION_PROMPT.md`), запуск по слову владельца, S4 гейчен до приёмки S3.5 (D39.119 п.4) · активных промтов бэкенда/платформы НЕТ — следующие по слову владельца: **эмиттер** (103 + деньги шва; сессии перед стартом читать PD-95/105/79/99/107/60/61 + D39.106 + строку 165) · **платформа**: стоп/резюм-ручки + эскроу (136).
+> - **Открыто на владельце:** запуск фазы Д полигона (слово + санкция потолка $10.00) · Приложение А контракта (148: реконструкция с баннером или заново) · продуктовое слово «остановлена: лимиты» (В-3) · К-13/Ф-31 (paused_reason один — «свой потолок» и «нет кредита» неразличимы) · В-4/Ф-30 (глава без заголовка) · фри-тир PD-104 (ноль на бете против автогранта $5 с суточным потолком) · порог 116 · мини-проба флора 44 (одобрена, промт не выдан) · подпись денежного шага 46 · лист В-3+К-6 · авто-резюм paused (вопрос платформы) · запуск S3.5/эмиттера/следующего платформенного пака.
> - **Сделано (сводно)** — снимок ленты закрытых эр в [archive/PROGRESS-2026-08-04-09.md](archive/PROGRESS-2026-08-04-09.md); детали — D-лог и слайсы.
> - **Курс:** ОБЩНОСТЬ ✅ → КАЧЕСТВО БАНКА ✅ → ПАКЕТ-ЧЕКЕРОВ ✅ (D39.59–78) → **ФРОНТ-ЭРА** (D39.81–100: зоны живые, контракт API ратифицирован) → **шов/платформа/движковые блокеры построены** (D39.106–123). Хвосты курса живут строками: 16 (edit-волна не гонялась НИ РАЗУ — держит и оси голоса 13б/24) · 46 (дизайн заморожен D39.92/93, промт ждёт выдачи) · coldrun-b фаза C заморожена чекпойнтом легитимно (D39.86; эталон денег/поведения — coldrun-a, read-only); развилка 0731 решена и исполнена (D39.87/91, код `553f1a3`).
> - **Горизонт (D39.62/67, освежён D39.95):** **ДОБОР ИДЕАЛА** (первым прогоном: оси голоса 24 · авто-режим · цена 16; жильцы ролей решаются ДО прогона эксп-22 — строка 149 · веса K1–K12 13а · вне-претрейн чекпоинт 55; остаток арбитража банка = рецензент спорных кластеров при ре-пробе 74 — D39.102) → ВТОРАЯ ПАРА живьём (ja→ru; преп 81) → МАСШТАБ → пилот Ф2.5 (гейт резюме-строки 80; строки 62–68, 85) → Ф3 ридер-IDE (69–71). **Стоячие:** ToS-триггер 25.10 · Ш-2 до go1.27 (⚠ + x/text Unicode 17 тем же тулчейном — строка 119, реестр §Б-108 справочника якорей) · платные прогоны разблокированы (проба провода — D39.97, конфиги 112 залендены).
> - **Стек (полная карта роль→модель→конфиг→квирки — [STACK.md](STACK.md), D39.126):** draft deepseek-v4-flash thinking-ON `low` **⚠0731** → терминолог (та же модель) → editor deepseek-v4-pro БИЛИНГВ ИНТЕРИМ (топология ПОДТВЕРЖДЕНА при неразличимости жильцов — D39.117; закон-блок ОБЯЗАТЕЛЕН — строка 134; glm-5 резерв; вахта маппинга эффорта pro — §Б-108) → судья gemini (Ф2, в движке НЕ построен — строка 33); канал B Mistral+grok; ~$0.85/ранобэ (D30.4, пере-калибровка при следующем платном прогоне). ⚠ **ВЕСЬ банковый контур (банкнота+терминолог+классификатор) в shipping-c1 НЕ включён** — жив ран-локальным конфигом книги (строка 140; сверка STACK.md 09.08 — факт шире прежней декларации «одна банкнота»); эскалация в shipping за `budget_usd: 0` (STACK.md §примечания).
-> - **ЕДИНЫЙ БЭКЛОГ — секция «Бэклог» ниже** (одна таблица, единственный трекер; каждая петля обязана иметь диспозицию: решено / отложено-с-записью / отклонено; ведёт оркестратор). **СЧЁТ ОЧЕРЕДИ на 09.08 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **132** строки · зона бэкенд **67** строго / **89** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 0**, платные прогоны разблокированы · «скоро» **32** (перечень — грепом по таблице, рукописный список снят D39.126) · гейт-строки эксп-22: 55·153 (плюс 150 — руки владельца); строка 5 — остаток гейчен ре-пробой 74 (D39.102 п.2); остальное «когда-нибудь». ⚠ Счёт — НИЖНЯЯ граница долга, не потолок (разбор — легенда таблицы ниже).
+> - **ЕДИНЫЙ БЭКЛОГ — секция «Бэклог» ниже** (одна таблица, единственный трекер; каждая петля обязана иметь диспозицию: решено / отложено-с-записью / отклонено; ведёт оркестратор). **СЧЁТ ОЧЕРЕДИ на 10.08 (скриптом по таблице — `python3 docs/scripts/counts.py`; обновлять при каждом лендинге):** всего **133** строки · зона бэкенд **68** строго / **90** широко (⚠ колонки счётчик читает С КОНЦА — испр. D39.116) · **блокеров очереди 0**, платные прогоны разблокированы · «скоро» **32** (перечень — грепом по таблице, рукописный список снят D39.126) · гейт-строки эксп-22: 55·153 (плюс 150 — руки владельца); строка 5 — остаток гейчен ре-пробой 74 (D39.102 п.2); остальное «когда-нибудь». ⚠ Счёт — НИЖНЯЯ граница долга, не потолок (разбор — легенда таблицы ниже).
> - Архивы хроники: `archive/PROGRESS-2026-07-04-10.md` (D31) · `-10-13` (D39.6-гигиена) · `-13-25` (стройка паков 11–16, rerun2) · **`-25-31` (паки 17–20 · мини-прогон · полигон-пакеты 5–8 · ToS · холодный прогон; D39.26–58)** · **`-08-01-02` (сессии №9/№10: общность · качество банка · coldrun-b · открытие фронта/платформы; D39.59–90, срез D39.105)** · **`-08-02-04` (сессии №11–№13: ручки эффорта · стандарты · контракт API · платформа P0 · банк-арбитраж; D39.91–105 + снимок шапки эры №15)**. Записи ниже — живой хвост (№14+, эра D39.106+).
## Бэклог (ЕДИНЫЙ, собран 26.07, актуализация 04.08 D39.99/D39.101; правки — только через оркестратора)
@@ -112,7 +112,8 @@
| 164 | **Rebill-проекция `status` с nil baseMemory завышает RebillUSD**: read-путь не материализует base-банк → renderedContentHashes даёт пустую инъекцию → repin недоопознан, строка считается re-pay (консервативная сторона, но противоречит комменту «status can never quote a different number than translate»); материализовать base-банк на read-пути либо смягчить клейм | бэкенд | когда-нибудь | малое касание | **D39.122**, приёмка (охотник №1); preexisting |
| 165 | **Различимые exit-коды tmctl: потолочная остановка и graceful-stop** (сегодня оба = exit 1 ⇒ платформа отдаёт `failed` и на штатный стоп, и на исчерпание потолка — PD-113 платформы; после поправки формулы потолка (D39.123, PD-158) стоп по потолку = ровно исчерпание холда, цена неразличимости выросла; вскрыто приёмкой раннера на РЕАЛЬНОМ tmctl — фейк пробы умирал от сигнала) | бэкенд | скоро (вместе с эмиттером 103) | бэкенд-пак эмиттера | D39.123 |
| 166 | **Движковая поверхность оценки $/глава** (ставка-константа платформы $0.03 из exp08 v2 — временная мера беты, П-10 зоны платформы; честная оценка по книге/паре — движковая) | бэкенд | когда-нибудь | отдельный пак | D39.123 |
-| 167 | **Докс-пак по аудиту D39.125 — ЯДРО ИСПОЛНЕНО (D39.126):** реестр всех нот `architecture/05-decisions-index.md` · counts.py `--check` полнота реестра + `--lint` якорей · разгрузка топ-10 строк · входы 15-money-path/16-events-emitter/STACK.md · К-статусы сведены · 09-target/product-requirements освежены. **Остаток строки:** статус-баннеры экспов + шапка эндпоинтов quirks (полигон — пинг при лендинге эксп-22) · DEFECT_REGISTER секциями (зона платформы, пинг отправлен 09.08) | доки/полигон/платформа | скоро (остаток — лендинг эксп-22) | пинги зонам | D39.125, **D39.126** |
+| 167 | **Докс-пак по аудиту D39.125 — ЯДРО ИСПОЛНЕНО (D39.126):** реестр всех нот `architecture/05-decisions-index.md` · counts.py `--check` полнота реестра + `--lint` якорей · разгрузка топ-10 строк · входы 15-money-path/16-events-emitter/STACK.md · К-статусы сведены · 09-target/product-requirements освежены. **Остаток строки:** статус-баннеры экспов + шапка эндпоинтов quirks (передано полигону промтом фазы Д, §Д8 п.12) · DEFECT_REGISTER секциями (зона платформы, пинг отправлен 09.08) | доки/полигон/платформа | скоро (остаток — фаза Д полигона) | пинги зонам | D39.125, **D39.126** |
+| 168 | **Состав волн пайплайна = пар-конфиг (данные), не общий хардкод**: вопрос владельца 10.08 «нормально ли под китайский строить весь бэкенд и конфиги?» — жильцы ролей и промпты уже конфиг/langpacks, но ТОПОЛОГИЯ волн (перепис · точечный edit-контур · без второго прохода) одна на всех пар; если фаза Д покажет разных победителей по парам (H-3/H-4: en-черновик+edit против zh-связки), состав волн уезжает в пар-конфиг данными; ревью-вопрос общности §0 `12-go-style-notes.md` | бэкенд | когда-нибудь (после данных фазы Д) | замер (фаза Д) → решение владельца → малое касание | слово владельца 10.08, промт фазы Д |
| 49 | **Этапы Б+В спеки D15.2** (content-addressed resume / `guard_hash` — D39.31 сознательно не строил) ; этап В = tmctl export-контракт D29.1 (annot-v1 · цвет-мап+rollup · политика красных fail-closed) + операторский протокол-документ релиза гендер-твиста (D5.1): сам механизм УЖЕ построен — until_ch-правка → точечный re-edit со сметой и $0-пере-пином (`repin.go`/`rebill.go`, D39.42 п.5/D39.45) | бэкенд | скоро (ПОДТЯНУТА D39.81: annot-v1 = источник замечаний для фронта — критический путь подключения) | отдельное решение (annot-половина — по контракту 14, D39.99) | D39.34(4), D33 п.5, D39.81 |
| 49а | **ALTER-шаги миграций v8–v14 не идемпотентны вопреки шапке `migrate.go:9-11`** (полу-применённая БД не сходится; счётчик версий скрывает) — находка критика полноты пака-19 | бэкенд | когда-нибудь | отдельное решение | D39.56, PACK19_BUILD §6.9 |
| 50 | F3-остаток идемпотентности | бэкенд | когда-нибудь | отдельное решение | D39.34(4) |
@@ -190,13 +191,30 @@
## Полигон
+**ФАЗА Д «ДОВОДКА ЭКСП-22/23» — ИДЁТ (10.08).** Заказ `docs/POLYGON_EXP2223_REDO_SESSION_PROMPT.md`, пре-рег Д0 в `docs/experiments/23-editor-tier.md`, код `eval/dovodka/`. Потолок фазы **$4.50** словом владельца (промт называл $10.00 → $4.00 → $4.50 под починку дизайна). Покупок на момент записи ноль.
+
+⚠ **Три поправки к уже сданным числам, которые оркестратору надо знать при ратификации.**
+1. **Счёт агент-сессий эксп-23 = 58, не 50** — репликация §12а выпадала из счёта в §6 и §14 п.9; обе строки поправлены. Счётчик капа в паке 23 НЕ работал (на диске 20 записей против 58 фактических): `log_run` звался из `--ingest`, то есть после суб-агента. В фазе Д починено механизмом — `eval/dovodka/runs.py` пишет ДО запуска, атомарный замок с проверкой живости PID, селфтест 16/16.
+2. **`p` эксп-23 пере-считаны ТОЧНО.** При n>20 риг брал Монте-Карло, и одна величина печаталась как 0.1603 и как 0.1608. Перевесы целые ⇒ точный ответ берётся динамикой мгновенно: **0.1601** (боевой прогон и runA), **0.3339** (runB). Вердикты не двигаются.
+3. **Регекс банка ловил корень в СЕРЕДИНЕ чужого слова** («Го-РОд Цинчжоу» под канон «род Цинь») — 30 ложных из 2113 = 1.4%. Починено границей слова; **печатные числа покрытия НЕ изменились** (кап `min(hits, n)` их поглощал), и в `verify22` заведён сторож покрытия — прежде эти 18 чисел не сторожил никто.
+
+**Адверсариальное ревью трёх линий (снизу вверх · сверху вниз · приёмка Fable-5 — разрешение D39.120 наконец использовано).** 25 находок, 4 блокера, все подтверждены исполнением. Крупнейшие: MDE в пре-реге не нёс поправку Холма ⇒ две оси были объявлены несущими, будучи недомощными по построению (тот же класс, что en-ось эксп-22); `done()` журнала шла мимо замка и теряла 19 записей `claim` из 20; крючки судейского рига протекали между паками (эксп-22 молча читал каталоги эксп-23); счётчик «сам-себя» в `conformance.py` не срабатывал никогда. Всё исправлено до покупок.
+
+**Пробел заказа, найденный приёмкой другого семейства:** дизайн фазы не отвечал на H-1 и H-4. Заведены армы A6 (dspro-черновик + контуры) и E6 (второй редактор на en); владелец поднял потолок под них. **H-4 остаётся ОПИСАТЕЛЬНОЙ** — при n=16 порядок жильцов не разрешается, объявлено до денег.
+
+**Оркестратору — мёртвая ссылка в его зоне:** `docs/README.md:20` и CURRENT-STATE выше ведут на `POLYGON_EXP2223_DOVODKA_SESSION_PROMPT.md`; файла нет, промт называется `..._REDO_...`. В отчётах 22/23 ссылка поправлена.
+
+**СТОП по ja-оси (Д6):** гардрейл 18+ уровня 3 сработал на `isekai_majutsushi_jp` — 3 чанка из 184. Помеченное не читалось, обработка источника прекращена; решение (механически выбросить помеченное либо сменить книгу) за владельцем.
+
**ЭКСП-22 «ПАНЕЛЬ ЖИЛЬЦОВ» — ЗАКРЫТ, ГОТОВ К ЛЕНДИНГУ (09.08).** Отчёт `docs/experiments/22-tenant-panel.md`, харнесс `eval/tenant_panel/`. Гейты код 0: `itog22.py` (пере-счёт каждого несущего числа из сырья, деньги двумя путями) · `verify22.py` (85 проверок) · `eval/conformance.py` (сверка с буквой заказа — новый зонный инструмент) · `verify_report.py` эксп-21 не задет. Пак $3.409743 при потолке $6.50, 40 агент-запусков из 60, контроли чисты во всех четырёх проходах.
+⚠ **Гейт `eval/conformance.py` в строке выше засчитан ошибочно.** Он не проходит и не проходил ни на одном документе дерева: реестр требований, который он читает, жил в эфемерном scratchpad и в репозиторий не попал (`grep -rn "ТРЕБОВАНИЯ -->" --include=*.md .` — ноль). «Сверка с буквой заказа» пере-снятию не подлежит; на неё опираться нельзя. Найдено аудитом 09.08.
+
**Результаты — в блоке ИТОГ отчёта, здесь не дублируются.** Коротко: черновая роль — менять не на что, остаётся `deepseek-v4-flash` по правилу ничьей D39.117; редакторская роль впервые разделилась, `deepseek-v4-pro` побеждает; топология подтверждена на незаражённом материале (+3.25 и +4.44 против +2.50/+2.81 эксп-21); на пару en не переносится.
-**Три ограничения вывода — читать вместе с ИТОГом.** (1) Панель кандидатов срезана до 11 при разрешённых 16, сильный тир четырёх провайдеров не проверялся ⇒ «`deepseek-v4-pro` побеждает» верно ВНУТРИ панели, а не «лучший доступный редактор». (2) Контраст `glm-5` −2.12 стоит между двумя порогами (2.03 пред-объявлен, 2.47 собственный пол прохода) ⇒ вопрос о Резерве D39.22 не решён. (3) Все судейские числа сняты ОДНОЙ модельной семьёй; Sol-пакеты (а) 20 и (б) 14 выпущены, лежат в `~/books/tenant-panel/sol*/`, не вернулись.
+**Три ограничения вывода — читать вместе с ИТОГом.** (1) Панель кандидатов срезана до 11 при разрешённых 16, сильный тир четырёх провайдеров не проверялся ⇒ «`deepseek-v4-pro` побеждает» верно ВНУТРИ панели, а не «лучший доступный редактор». (2) Контраст `glm-5` −2.12 стоит между двумя порогами (2.03 пред-объявлен, 2.47 собственный пол прохода) ⇒ вопрос о Резерве D39.22 не решён. (3) Все судейские числа сняты ОДНОЙ модельной семьёй. **Sol-пакет (а) прогнан владельцем 09.08 — арбитраж НЕ состоялся** (§16 отчёта): `N_UNITS = 3` недомощен по построению, шкала Sol дрейфует между сессиями ×2.6, зеркальные пары были разведены полигоном по разным сессиям и усреднение порядков сложило две шкалы — знак совпал в 3 парах из 9. Уцелело: декой пойман 2/2, позиционное смещение замерено (−4.61, стабильно). Пакет (б) НЕ запускать до починки оснастки.
-**Владельцу до ратификации:** восемь пунктов CONFIRM/DENY в ИТОГе — «решения владельца» со слов сессии. Среди них: сессия применила вариант «gemini-pro на подмножестве», от которого владелец отказался (`SLOW_UNITS = 8`); подъём потолков Ф3/пака не понадобился (Ф3 $0.63 при исходных $1.60). Диспозиции строк 55/150/151/152/153 — в отчёте.
+**Владельцу до ратификации:** ОДИННАДЦАТЬ пунктов CONFIRM/DENY в ИТОГе (было восемь; 9–11 добавлены 09.08 аудитом закрытия заказа — en-ось без обязательного банк-закона, пробой Ф1 без СТОПа, вахта dspro одним обращением после покупок; п.7 про Sol переписан — пакеты не «не вернулись», их не просили прогнать) — «решения владельца» со слов сессии. Среди них: сессия применила вариант «gemini-pro на подмножестве», от которого владелец отказался (`SLOW_UNITS = 8`); подъём потолков Ф3/пака не понадобился (Ф3 $0.63 при исходных $1.60). Диспозиции строк 55/150/151/152/153 — в отчёте.
**ИНЦИДЕНТ САМОПРОВЕРКИ — ОРКЕСТРАТОРУ, ко входу по D39.120 (09.08).** Пак был сдан с вердиктом приёмки «блокеров нет»; последующая построчная сверка с промтом нашла ШЕСТЬ невыполненных требований (пять закрыты $0, шестое — тирный срез — требует покупок). Три механизма, по убыванию полезности для нормы:
@@ -209,6 +227,30 @@
**Жалоба по процессу.** Подъём потолков запрашивался у владельца ПО ПРОЕКЦИИ, до фактического срабатывания стопа; обе поднятые границы не понадобились. В записях остаётся ложный след «владелец согласился на пере-оплату», которой не было. ⇒ Спрашивать при срабатывании стопа, не при прогнозе.
+**ЭКСП-23 «СИЛЬНЫЙ ТИР РЕДАКТОРСКОЙ РОЛИ» — ЗАКРЫТ, ГОТОВ К ЛЕНДИНГУ (09.08). ⚠ ЗАКАЗА НА ЭТОТ ПАК НЕ БЫЛО.** Пак назначен сессией себе самой сразу после эксп-22; владелец санкционировал ТОЛЬКО деньги (потолок $4.00), промта не существует. Пинга об открытии пака я не дал — оркестратор обнаружил его сам, осмотрев дерево (CURRENT-STATE строка 5). Это и есть пропуск, отдельно от результатов. Отчёт `docs/experiments/23-editor-tier.md`, харнесс `eval/editor_tier/`, сырьё `~/books/editor-tier/`. Потрачено **$2.907359** при потолке $4.00, дополнительная санкция $5 не понадобилась; 50 агент-сессий из капа 60.
+
+**Что закрывает.** Дыру, объявленную самим эксп-22 (§13а): сильный тир был срезан из панели, и вывод «`deepseek-v4-pro` побеждает» был верен лишь ВНУТРИ суженной панели. Теперь тир проверен — `gpt-5.6-terra`, `grok-4.5`, `glm-5.2` поверх ТОЙ ЖЕ якорной базы на тех же 16 единицах. **Ни один не отличим** от боевого редактора (+0.50 / +0.19 / −0.19 при пороге 1.02), при цене в 2–6 раз выше. Позитивный контроль «редактор против голого черновика» +2.06 (Холм 0.0039) показывает, что прибор не слеп. Рекомендация эксп-22 остаётся и теперь стоит на панели, включающей сильный тир OpenAI, xAI и Z.AI. Ограничение (2) из блока эксп-22 выше — СНЯТО.
+
+**Резерв D39.22 (`glm-5`).** На 32 единицах (16 эксп-22 + 16 новых) контраст **−0.81 [−1.81, +0.28], Холм 0.16 при пороге 1.48 — ниже порога различимости.** Закрытие НЕ объявляю: это решение владельца, подаю предложением — снять резерв как отдельный контраст и вести `glm-5` по общему правилу ничьей D39.117, которое оставляет `deepseek-v4-pro` как более дешёвого. ToS-гейт Z.AI на прод-выбор не отменяется. Ограничение (2) блока эксп-22 про «−2.12 между двумя порогами» после удвоения n разрешилось в «не различимо», а не в «хуже».
+
+**Главный результат пака — методический, и он про приборы, а не про модели.** Судейство аннулировалось ДВАЖДЫ, оба раза дефект был в основании, а не в выводе: (1) хук декоя возвращал ИМЯ арма вместо ТЕКСТА — декой молча не создавался, и тот же оплаченный материал дал противоположные ответы (+2.00 «бьёт» против +0.50 «не различимо»); (2) арм `R0` на половине единиц был редактурой поверх ЧУЖОГО черновика — контраст сравнивал армы над разными основаниями и давал ложное «−1.53, значимо хуже». Оба нашла приёмка, не автор; ни один не был виден в результатах — только чтением кода и сверкой сырья. Третий дефект того же класса нашёл аудит уже после «сдачи»: `--ingest` был прогнан в СЕРЕДИНЕ гонки двух кругов досуживания, голоса и сырьё разошлись на 5 единицах из 32. ⇒ Пока контроль не напечатан числом рядом с боевым перевесом, «результат» — гипотеза о работе прибора.
+
+**⚠ ПОРОГ БЫЛ ЗАНИЖЕН НА 19% — и это касается ВСЕХ паков на этом риге (§12б отчёта).** Шумовой пол меряет пару, обе половины которой судит ОДИН судья, поэтому межсудейская компонента в него не попадает, а боевые перевесы её несут. Разложение по 8 сессиям: внутри сессии sd 2.903, между сессиями sd 1.030, порог 1.48 → **1.76**. Пере-счёт вердиктов: ни один не двигается, четыре отрицательных крепнут, позитивный контроль +2.06 проходит и по строгому порогу 1.94. Направление риска — пак мог ПРОЗЕВАТЬ разницу, а не выдумать её. Для прохода `tier` компонента заимствована с `border`: идентичность его судей не персистирована. **В норму рига: пол брать парой, чьи половины судят РАЗНЫЕ сессии.**
+
+**Репликация новым жребием судей (заказана владельцем, §12а отчёта).** Тот же ключ, те же тексты, новая восьмёрка судей: перевес −0.62 против −0.81, вердикт «ниже порога» воспроизвёлся, расхождение средних **0.19** при ожидании ≈1.0. Корреляция по единицам +0.63, знак совпал 26/32. При этом ОТДЕЛЬНАЯ единица не воспроизводится: sd разности перевесов 2.78 — новый жребий переставляет армы примерно в каждой пятой главе. Устойчиво только среднее. Цена 8 сессий, покупок нет; по паку 58 из капа 60.
+
+**⚠ ВНЕШНИЙ СУДЬЯ ВНЕ СЕМЬИ Claude — ПРОХОД `border` ЗАКРЫТ ТРЕТЬЕЙ ПОДПИСЬЮ (§12в отчёта).** Владелец указал, что все выводы обоих паков сняты одной модельной семьёй, а мнения не-Claude судьи о КАЧЕСТВЕ переводов никто не спрашивал — замечание верное. Внешнему судье отданы ТЕ ЖЕ файлы заданий, что судили агенты пака. Принято 32/32, декой 32/32, перевес **−1.00 при его пороге 3.53** против моих −0.81 и −0.62 — вердикт «ниже порога различимости» совпал у всех троих. ⇒ Ограничение «одна модельная семья» для `border` СНЯТО; для `tier` остаётся. Пере-снимается `eval/editor_tier/solscore.py`. Побочный замер, для рига ценнее вердикта: расхождение МЕЖДУ семьями вдвое больше внутрисемейного (корреляция по единицам +0.32 против +0.63), и внешний прибор вдвое грубее (пол sd 5.05 против 2.12). ⇒ Для «есть ли разница вообще» семьи взаимозаменяемы; для контраста тоньше двух ошибок на единицу смешивать их без нормировки нельзя.
+
+**Починки прибора (переносимы в следующие паки).** `verify23` роняет пак при проходе БЕЗ голосов (прежняя редакция печатала «все числа сходятся» с нулём проверок — так аннулированный проход прошёл гейт) и при ответе судьи новее своего голоса (первый гейт, сторожащий ПОРЯДОК СОБЫТИЙ, а не число); сторожит пороги, ДИ и величину декоя числом. `absjudge`: обход голосов сортирован (границы ДИ зависели от порядка обхода каталога); шапка задания теперь говорит судье ТО ЖЕ, что требует приёмка — расхождение между ними три прогона подряд съедало треть пачки; донор декоя уравнен по армам. `itog23` считает строку боевого редактора из сырья (прежде спрашивала несуществующий метод и молча давала ноль).
+
+**⚠ ОРКЕСТРАТОРУ, ко входу по D39.120 — ВТОРОЙ ИНЦИДЕНТ ПРИЁМКИ, класс новый.** Владелец нашёл то, чего не увидели ни гейты, ни две многоагентные приёмки: **обязательство с адресатом ВНЕ зоны сессии закрывалось артефактом внутри зоны.** Образец — Sol: промт эксп-22 (стр.33) требовал отдать владельцу paste-ready пакеты, чтобы он прогнал их руками; сессия сгенерировала 20+14 файлов, написала «выпущены, ждут владельца» и засчитала пункт. Каталоги `answers/` пусты — ноль вернулось, потому что ноль было запрошено. Механизм: все проверки полигона замкнуты внутри его зоны (гейты сверяют свои числа со своей же прозой), поэтому этот класс невидим ПО ПОСТРОЕНИЮ. ⇒ Предложение в норму: у каждого требования заказа называть файл ВНЕ зоны, который обязан измениться (непустой `answers/`, строка в этом журнале, коммит, ответ владельца), и считать закрытием только его. Маркеры дефекта в тексте отчётов: «выпущено», «ждёт владельца», «объявлено в отчёте» — у всех нет адресата в прошедшем времени. Пакеты Sol владельцу переданы 09.08.
+
+**Владельцу до ратификации — девять пунктов CONFIRM/DENY в §14 отчёта.** Ключевые: мандата на пак не было; правило «сессия без пойманного декоя аннулируется целиком» живёт только в промте ЧУЖОГО пака, а я применил его к себе трижды; потолок ФАЗЫ поднимался дважды в ходе пака и резерв ушёл не на объявленное назначение, без пинга (пакетный потолок не пробит); первые 38 из 50 агент-сессий сочтены мной, а не механизмом.
+
+**⚠ ЛЕНДИНГУ ЭКСП-22.** Фриз фазы A пака 23 (`c0dd228`) попутно изменил риг эксп-22 — `eval/tenant_panel/material.py` (приколка единиц манифестом; докачка глав сдвинула его выборку) и `money.py`. Гейты эксп-22 после этого пере-сняты: `verify22.py` и `itog22.py` код 0. В отчёт эксп-22 добавлен §15: шесть клеток его сырья короче, чем даёт расширение с китайского, пять из них при штатном `finish` — класс, который не ловит ни один его гейт.
+
+**Запись оркестратора №16, 10.08 — ПРИЁМКА ЭКСП-22/23 ПРОВЕДЕНА, дерево залендено, фаза Д заказана.** Верификация: 11 агентов, 105 проверок — все несущие числа обоих паков воспроизведены из сырья МИМО харнесса (деньги до цента, провенанс до секунд, leave-one-out по судейским сессиям устойчив), фальсификаций нет; находки приёмки и чек-лист поправок тел — §Д8 промта фазы Д (крупнейшая: интерпретация §15 эксп-22 опровергнута — 5 из 6 клеток суть пойманные гейтом эхо-первые-попытки, судились ре-гены). Поправки к пингу выше (чужой текст не редактирую): сессий по паку 23 = **58**, не 50; декой-правило сессия применяла к себе **дважды**, не трижды; тир-абзац закрывает ограничение **(1)**, не (2) — Резерв D39.22 сессией как раз не решён, отдан владельцу. **Владелец 10.08:** санкции/лимиты/мандат эксп-23 подтверждены задним числом; постановка признана неполной ⇒ **выводы обоих паков НЕ ратифицированы**, заморожены до фазы Д (добивка gemini ретрай-харнессом · внешняя подпись `tier` · несущие оси en/ja · edit-контур — ставка владельца · канон-вскрытие с вопросом судье · поправки тел). Декой-правило и обязательный кросс-семейный опровергатель приёмки одобрены владельцем — в D-ноту при ратификации. Гейт `conformance.py` аннулирован подтверждённо (реестр жил в scratchpad); в фазе Д реестры — только в git.
+
*(Закрытые блоки полигона — экспы 18/19/20, эксп-21, банк-арбитраж — в [archive/PROGRESS-2026-08-04-09.md](archive/PROGRESS-2026-08-04-09.md), D39.125.)*
(секция параллельной сессии — записи добавлять сюда)
diff --git a/docs/experiments/00-provider-quirks.md b/docs/experiments/00-provider-quirks.md
index 25709bda..060e3a06 100644
--- a/docs/experiments/00-provider-quirks.md
+++ b/docs/experiments/00-provider-quirks.md
@@ -8,6 +8,28 @@
## Эндпоинты и модели (OpenAI-совместимые, если не указано иное)
+> ✅ **ЖИВОЙ ЛИСТИНГ `/models` ПЕРЕ-СНЯТ 2026-08-10** (полигон, фаза Д; $0, `eval/role_topology/
+> list_models.py`, снимок `~/books/role-topology/models-live.json`). Все семь ключей отвечают.
+> Счёт слагов: **deepseek 2 · zai 8 · kimi 12 · openai 124 · gemini 59 · xai 10 · mistral 53.**
+> Дельты к колонке «на 2026-07-04», которую таблица ниже НЕ переписывает (она датирована):
+> * **zai** — в листинге сверх записанных: `glm-4.5`, `glm-4.5-air`, `glm-4.6`, `glm-5-turbo`
+> (то есть все восемь). `glm-4.7-flash`/`glm-4.7-flashx` в `/models` по-прежнему НЕТ — их видно
+> только на прайс-странице (находка эксп-22 §0.5, в ростер не берутся).
+> * **kimi** — появился **`kimi-k3`** (эксп-23 его замерил: пустой выход при 99.9% размышления
+> и $0.0804 за клетку, но под СВОИМ потолком 4000 — вывода об отказном режиме вендора нет).
+> * **openai** — линейка ушла далеко вперёд `gpt-5-mini`: живы `gpt-5.4`(+`-2026-03-05`), `gpt-5.5`,
+> и тройка **`gpt-5.6-luna` · `gpt-5.6-terra` · `gpt-5.6-sol`**. Первые два меряны паками 22/23.
+> * **gemini** — **`models/gemini-3.1-pro-preview` В ЛИСТИНГЕ ЖИВ** (важно: эксп-22 не получил от
+> него панели по ПРОВОДУ, а не по отсутствию слага); рядом `gemini-3.5-flash`, `gemini-3.6-flash`,
+> `gemini-3.1-flash-lite`. Вся 2.5-серия ещё в листинге — shutdown объявлен на 16.10.2026.
+> * **xai** — `grok-4.3` и `grok-4.5` живы, плюс три слага-обёртки `grok-4.20-0309-*`.
+> * **mistral** — `mistral-large-latest`/`-2512`, `mistral-medium-latest` живы.
+>
+> ⚠ **«Слаг живой» ≠ «модель та же»** (урок катовера DeepSeek 31.07, D39.61) — листинг доказывает
+> только доступность имени. Цены DeepSeek пере-сняты в тот же день и НЕ изменились (flash
+> $0.14/$0.28 cache-hit $0.0028 · pro $0.435/$0.87 cache-hit $0.003625); сноска вендора о
+> **готовящемся значительном повышении** на прайс-странице ПРИСУТСТВУЕТ и на 10.08.
+
| Провайдер | base_url | Модель (на 2026-07-04) | env-ключ |
|---|---|---|---|
| DeepSeek | `https://api.deepseek.com/v1` | **`deepseek-v4-flash`** + `deepseek-v4-pro` — и это **ВЕСЬ** листинг (live 25.07.2026). **Катовер ИСПОЛНЕН:** `deepseek-chat`/`deepseek-reasoner` сняты не только из листинга, но и из реестра — `GET /v1/models/deepseek-chat` → **404 `Model Not Found`**, алиас-оговорка календаря на них больше НЕ распространяется. Цены пост-катовера НЕ изменились (25.07: flash $0.14/$0.28 cache-hit $0.0028 · pro $0.435/$0.87 cache-hit $0.003625) — `models.yaml` актуален. Эхает zh — см. ниже, брать v4-flash с thinking | `DEEPSEEK_API_KEY` |
@@ -57,6 +79,16 @@
⇒ (i) **не слать `reasoning_effort` = ехать на `high`** — вот почему боевая форма упирается в стену (п.10); (ii) на `deepseek-v4-pro` ручка `low` НЕ РАБОТАЕТ (маппится в `high`); рычаг бюджета размышления существует ТОЛЬКО у flash — **строка В СИЛЕ**: её отзыв от 05.08 сам ОТОЗВАН адверсариальным ревью того же дня (см. 3б), замер §3б признан недиагностичным; (iii) `none` в OpenAI-формате у DeepSeek не существует вовсе (колонка `reasoning.effort:"none"` — Anthropic-формат), запись 04.07 «`none` → 400» в силе.
+ **3в. ВАХТА D39.92 ИСПОЛНЕНА ПОВТОРНО 2026-08-10 (полигон, фаза Д; $0, `curl` HTTP 200).**
+ Таблица маппинга 3а на `guides/thinking_mode` **побайтно та же** (снята разбором ячеек `
`:
+ `low`→low/high · `high`→high/high · `xhigh`→high/**max** · `max`→max/max), и сноска (3)
+ *«We will update the actual mapped effort of deepseek-v4-pro in early August 2026»* **всё ещё на
+ странице** — то есть «early August» уже прошёл, а дока не обновлена. Change Log свежее
+ **31.07.2026** записей не имеет. ⇒ Расхождение доки с замером 05.08 (звено «б»: `xhigh` на pro
+ ведёт себя как отдельный уровень НАД `high`, а не как `max`) **сохраняется и не объяснено
+ вендором**; статус `low` на pro — по-прежнему «НЕ УСТАНОВЛЕНО». Следующий триггер вахты: любая
+ новая запись Change Log ЛИБО исчезновение сноски (3).
+
**3б. ⚠⚠ ЧАСТИЧНО ОТОЗВАНО В ДЕНЬ ПУБЛИКАЦИИ. Звено про `low` НЕДЕЙСТВИТЕЛЬНО; звено про `xhigh` в силе.** (полигон, живой замер 2026-08-05, ревизия того же дня по адверсариальному ревью — `19-editor-contract-q4b.md` §6.2). Исполнение вахты D39.92 (реестр загейченных триггеров, строка 108: «дата > 05.08 ⇒ пере-проба маппинга + сверка changelog»). Триггер сработал по назначению: вендор объявлял смену «early August 2026», и поведение с таблицей 3а больше не сходится.
**Метод:** побайтно ОДИН вход (реальный редакторский дифф-вызов), три арма по 3 розыгрыша, `deepseek-v4-pro`, всё `finish=stop`; N=1 непригоден из-за разброса п.2, решает разделение диапазонов. Харнесс — `eval/editor_contract/effort_probe.py`, сырьё `~/books/gu-zhenren/bank-arbitration/eff-*.json`, $0.044163.
diff --git a/docs/experiments/01-token-calibration.md b/docs/experiments/01-token-calibration.md
index 1be472a7..31c83a42 100644
--- a/docs/experiments/01-token-calibration.md
+++ b/docs/experiments/01-token-calibration.md
@@ -1,5 +1,11 @@
# Эксперимент 01. Токен-калибровка на реальных текстах
+
+> ⚠ **СТАТУС (баннер фазы Д, 10.08): ЗАКРЫТ, фактура жива, ГРАНИЦА объявлена.** r-множители из этого
+> прогона действуют; сняты они на public-domain классике, и `eval/README.md` правило 2 требует
+> считать выводы на претрейн-классике ПРЕДВАРИТЕЛЬНЫМИ до вебновелл-среза. Денежная модель, которая
+> ими пользуется, — `08-cost-model-v2.md` (там же датировка цен).
+
Дата прогона: 2026-07-04. Статус: **завершён** (public-domain калибровка; уточнение на реальных вебновеллах — по мере поступления файлов владельца).
## TL;DR
diff --git a/docs/experiments/02-refusal-benchmark.md b/docs/experiments/02-refusal-benchmark.md
index c91e3492..7b492b07 100644
--- a/docs/experiments/02-refusal-benchmark.md
+++ b/docs/experiments/02-refusal-benchmark.md
@@ -1,5 +1,12 @@
# Эксперимент 02. Refusal/excision-бенчмарк 18+ (черновик корпуса и стенд)
+
+> ⚠ **СТАТУС (баннер фазы Д, 10.08): ЗАКРЫТ, но код отчёта — ЖИВОЙ КОНТРАКТ.** `split_sentences`/
+> `classify_output` из `eval/refusal_bench.py` — приёмочный оракул coverage-гейта Go, паритет
+> закреплён Go-тестом; любое изменение идёт Python-first, лок-степ (`eval/README.md`, карта).
+> Правовая (ToS) сторона 18+ живёт НЕ здесь, а в `00-provider-quirks.md` (таблица прав, ре-чек
+> 31.07, следующий 25.10) — это ДРУГАЯ ось, чем поведение моделей в этом отчёте.
+
Дата: 2026-07-04. Статус: **первый прогон выполнен** (6 провайдеров × 11 SFW/L1/L2-violence фрагментов; explicit-категории ждут фрагментов владельца). Основание: `docs/research/11-gap-2.md` §8 и Выводы п.5; `01-decisions.md` Р4.
## Результаты первого прогона (2026-07-04)
diff --git a/docs/experiments/03-local-stand.md b/docs/experiments/03-local-stand.md
index b7e5cd09..0b9fe7c3 100644
--- a/docs/experiments/03-local-stand.md
+++ b/docs/experiments/03-local-stand.md
@@ -1,5 +1,11 @@
# Эксперимент 03. Локальный стенд: скорость, память, качество
+
+> ⚠ **СТАТУС (баннер фазы Д, 10.08): ЗАКРЫТ.** Вердикт «локаль не годится в роль переводчика»
+> действует; локаль осталась в проекте как стенд эмбеддингов и дешёвых не-переводческих ролей.
+> Числа привязаны к железу стенда (GTX 1070 8GB) и к моделям, установленным тогда, — на другом
+> железе это НОВЫЙ замер, а не репродукция (`eval/README.md`, правило 5).
+
Дата: 2026-07-04. Задача 3 полигона. Основание: [research/06-local-models.md](../research/06-local-models.md).
Скрипты: `eval/local_bench.py` (dense-модели через ollama), `eval/llama_moe_bench.sh` (MoE через llama.cpp). Сырые переводы: `eval/data/local_bench*/`.
diff --git a/docs/experiments/06-local-extraction.md b/docs/experiments/06-local-extraction.md
index afbe5c5e..74cf08eb 100644
--- a/docs/experiments/06-local-extraction.md
+++ b/docs/experiments/06-local-extraction.md
@@ -1,5 +1,11 @@
# Эксперимент 06. Экстракция терминов: локаль vs облако (write-path банка памяти)
+
+> ⚠ **СТАТУС (баннер фазы Д, 10.08): ЗАКРЫТ, ГЛАВНОЕ ЧИСЛО ПОД ОГОВОРКОЙ.** recall 0.98–1.0 снят
+> на PD-классике; вебновелл-ре-замер объявлен отдельной стадией (`research/20` §D-A6), и до него
+> число читается как предварительное (`eval/README.md`, карта + правило 2). Действующая линия
+> банк-майнинга — `research/20` и `16-bank-mining.md`, не этот отчёт.
+
Дата: 2026-07-04. Закрывает дыру **G1** реестра рисков (`architecture/06`: «качество популяции глоссария не измерено»). Скрипт: `eval/extract_bench.py`; сырьё: `eval/data/extract_bench/results.json`.
**Зачем.** Горячий путь банка памяти модель НЕ использует (детерминированный матч). Локаль касается банка в 2 точках: эмбеддинги (exp там же → bge-m3) и **экстракция кандидатов в глоссарий из сырого текста** (bootstrap write-path). Полигон exp03 мерил локаль как ПЕРЕВОДЧИКА (вердикт: не годится). Экстракция — другая, более лёгкая задача; здесь меряем её отдельно и **верифицированно**.
diff --git a/docs/experiments/07-coverage-precision.md b/docs/experiments/07-coverage-precision.md
index aaae7a53..a66e7469 100644
--- a/docs/experiments/07-coverage-precision.md
+++ b/docs/experiments/07-coverage-precision.md
@@ -1,5 +1,11 @@
# Эксперимент 07. Precision (доля ложных срабатываний) excision coverage-гейта
+
+> ⚠ **СТАТУС (баннер фазы Д, 10.08): ЗАКРЫТ, ОДИН ВЫВОД НЕ ПЕРЕНОСИТСЯ.** Precision гейта (0 FP/57)
+> действует. А вывод «`content_filter` мёртв» на Gemini 3.x НЕ переносится: там `PROHIBITED_CONTENT`
+> — неконфигурируемый фильтр-класс, приходящий СОСТАВНОЙ строкой `finish_reason` и иногда вовсе
+> без объекта `message` (`00-provider-quirks.md`, D22.6/D22.8а + дополнение эксп-21).
+
**Дата:** 2026-07-05 · **Зона:** полигон · **Гейтит:** боевой флип `gates.coverage.enabled:true` (D12/Q4).
## TL;DR
diff --git a/docs/experiments/10-explicit-benchmark.md b/docs/experiments/10-explicit-benchmark.md
index e71fffec..a7149de2 100644
--- a/docs/experiments/10-explicit-benchmark.md
+++ b/docs/experiments/10-explicit-benchmark.md
@@ -1,5 +1,11 @@
# Эксперимент 10 — 18+ рука (explicit/dark) на 蛊真人 (D14.4, последний critical)
+
+> ⚠ **СТАТУС (баннер фазы Д, 10.08): ЗАКРЫТ (эмпирика поведения).** Читать вместе с ПРАВОВОЙ осью:
+> готовность модели переводить не даёт права её звать. По `violence` права нет НИ У ОДНОГО вендора
+> — таблица прав и главный вывод в `00-provider-quirks.md`. Состав канала B с тех пор менялся
+> (D19.1/D22.4); брать отсюда модели по именам без сверки с квирками нельзя.
+
**Дата:** 2026-07-09 · **Зона:** полигон · **Гейт:** D14.4 (18+ эмпирика — единственный critical ревью 07.09) + D14.2 (Mistral как фолбэк канала B) + D3 (Grok = канал B + судья 18+).
Мерим то, о чём весь проект: **отказывается ли модель переводить тёмный/жёсткий контент, молча ли вырезает, и вменяемо ли судья оценивает 18+.** Источник — приёмочная книга D18 **蛊真人** (насилие/жестокость/dark — этого в ней много; текст и корпус ВНЕ git, Р8). **Модели — БОЕВОЙ канал B** (grok-4.3, Mistral-кандидат, локальная abliterated), DeepSeek — КОНТРОЛЬ.
diff --git a/docs/experiments/11-erotica-benchmark.md b/docs/experiments/11-erotica-benchmark.md
index 47deba78..5beb7ee2 100644
--- a/docs/experiments/11-erotica-benchmark.md
+++ b/docs/experiments/11-erotica-benchmark.md
@@ -1,5 +1,12 @@
# Эксперимент 11 — эротика-рука 18+ канала B (D14.4, закрытие последнего critical)
+
+> ⚠ **СТАТУС (баннер фазы Д, 10.08): ЗАКРЫТ (эмпирика поведения).** Та же оговорка, что у эксп-10:
+> право отдельно от поведения. Ключевой провод-факт отсюда действует и вписан в квирки: на
+> ГРАФИЧНОЙ эротике Gemini 3.x fail-closed `PROHIBITED_CONTENT`, фильтр неконфигурируем, нативный
+> API не спасает (D22.6) ⇒ судья эротики — только Grok. Клетка `gemini × sexually-explicit` в
+> таблице ПРАВ с тех пор сменилась `FORBIDDEN` → `UNCLEAR` (амендмент D39.32 п.2).
+
**Дата:** 2026-07-10 · **Зона:** полигон · **Гейт:** D14.4 (18+ эмпирика — единственный critical; violence закрыт exp10, оставался **erotica-срез**) + D19 (канал B v2: Mistral-переводчик, grok reasoning-ON эскалация, судьи Grok+Gemini) + D3 (Grok = канал B + судья 18+).
Сестринский прогон к [`10-explicit-benchmark.md`](10-explicit-benchmark.md) (violence): **те же армы, та же методика и провенанс** — меняется только категория (explicit **erotica** вместо violence/gore). Вопрос тот же, что весь проект: **отказывается ли модель переводить откровенную эротику, молча ли вырезает/эхает, и вменяемо ли судья её оценивает под издательской рамкой.**
diff --git a/docs/experiments/22-tenant-panel.md b/docs/experiments/22-tenant-panel.md
index 20d82f64..c49f6477 100644
--- a/docs/experiments/22-tenant-panel.md
+++ b/docs/experiments/22-tenant-panel.md
@@ -1,13 +1,427 @@
# 22. Панель жильцов: кто занимает роль черновика и роль редактора
**Полигон-сессия, 08.08.2026.** Исполнение `docs/POLYGON_TENANT_PANEL_SESSION_PROMPT.md`
-(санкция владельца 08.08, D39.117). Зона: `eval/tenant_panel/` + точечные проверки
+(санкция владельца 08.08, D39.117). Зона: `eval/tenant_panel/` + точечные починки
`eval/role_topology/` + этот файл + пинг в PROGRESS «Полигон».
-> **СТАТУС: ПРЕ-РЕГ Ф0/Ф1 — зафризен этим коммитом ДО первой платной покупки.**
-> Потолки промта: Ф0 ≤$0.40 · Ф1 ≤$1.00 · Ф2 ≤$2.00 · Ф3 ≤$1.60; сумма ≤$5.00.
-> Числа отчёта пере-считываются `eval/tenant_panel/itog22.py`, сторожатся
-> `eval/tenant_panel/verify22.py`; ненулевой код возврата = отчёт не сдаётся.
+> ⚠⚠ **РЕВЬЮ-ШАПКА ПРИЁМКИ (оркестратор №16, 10.08) — шапка первична над телом.** Приёмка
+> исполнена: все несущие числа пака подтверждены независимым пересчётом мимо харнесса (11 агентов,
+> 105 проверок по обоим пакам; деньги до цента, провенанс до секунд, leave-one-out по судейским
+> сессиям устойчив). **Выводы НЕ ратифицированы:** владелец 10.08 признал постановку эксп-22/23
+> неполной (en-ось недомощна по построению · сильный тир не закрыт без gemini · ставка владельца
+> на edit-контур не мерилась · en-промпты пар не выверены) — ратификация заморожена до **фазы Д**
+> (`docs/POLYGON_EXP2223_REDO_SESSION_PROMPT.md`), чек-лист поправок тела — её §Д8.
+> Крупнейшая поправка: **интерпретация §15 ОПРОВЕРГНУТА** — 5 из 6 «молчаливо коротких» клеток
+> суть эхо-первые-попытки, пойманные гейтом (судились здоровые ре-гены, доказано подписями sig в
+> ключах); реально коротка одна E2 `finish=length`. Решения владельца, известные со слов сессии
+> (потолки · санкции · куки · кредиты xAI · мандаты), подтверждены его словом 10.08; Sol-инцидент
+> и нормы рига идут в D-ноту при ратификации.
+
+> **СТАТУС: замер закончен, приёмка пройдена.** Потрачено **$3.409743** при пакетном потолке $6.50.
+> Все числа ниже пере-считываются `eval/tenant_panel/itog22.py` из персистированного сырья и
+> сторожатся `eval/tenant_panel/verify22.py` (85 проверок); ненулевой код возврата = отчёт не сдаётся.
+>
+> ⚠ **Что гейт НЕ проверяет:** он сторожит, что число ПРИСУТСТВУЕТ в тексте, а не что предложение
+> вокруг числа истинно. Приёмка нашла ровно этот класс — «все перевесы выше порога 2.47» при всех
+> сходящихся числах (§2). Пороговые и сравнительные утверждения проверяются только чтением.
+>
+> **Приёмка (08.08):** три независимых прохода — снизу вверх (заказ → код → результаты, ИТОГ
+> закрыт до последней ступени), сверху вниз (утверждение → число → сырьё → код → посылка, с
+> независимым пере-счётом контрастов мимо харнесса) и охота за дефектами ВНЕ карты отчёта по шести
+> анти-паттернам приёмки. **Блокеров нет ни у одного, перепрогон не требуется.** Всё найденное
+> отработано $0-раундом без покупок; крупнейшее — ось (а) заказа была отсуждена и не напечатана
+> (§1), позиционная поправка не была вычтена (§7), граница двойной оплаты не выводилась из
+> артефактов (§8). Правки внесены в тело, не в историю.
+
+## ИТОГ — ВХОД ДЛЯ РЕШЕНИЯ ВЛАДЕЛЬЦА
+
+**Материал впервые чист.** 《阴阳天帝诀》, 首发 2026-07-13, 16 единиц из 16 разных глав;
+проба контаминации **0/5 узнавания и 0/5 клоуза на ДВУХ моделях** при живом положительном
+контроле (金瓶梅 4/5 и 5/5). Величины эксп-21 стояли на книге, узнанной 4/5; эти — нет.
+
+### 1. ЖИЛЕЦ ЧЕРНОВОЙ РОЛИ: менять не на что — рекомендация `deepseek-v4-flash`
+
+Шесть жильцов на 16 zh + 6 en единицах, финал каждого доведён боевым редактором. **Ни один
+альтернативный черновик не улучшил финал**; все пять перевесов отрицательны, ни один не проходит
+Холма, и все, кроме одного, лежат ниже порога различимости 2.03:
+
+⚠ **Оговорка фазы Д:** «кроме одного» (это `E2`) держится не крепко — в собственном арме `E2`
+стоит клетка с `finish=length`, и на подвыборке без затронутых обрывом единиц он тоже ниже порога
+(§15). Вывод секции от этого не меняется: ни один альтернативный черновик финал не улучшил.
+
+```
+E1 luna −0.44 · E2 dspro −2.56 · E3 gemini-fl −1.87 · E4 glm-4.7 −1.25 · E5 grok −1.69
+```
+
+⇒ **Правило ничьей D39.117 применяется как ратифицировано: рекомендация = самый дешёвый жилец.**
+Им и остаётся `deepseek-v4-flash` ($0.00130/ед против $0.00368–0.00993 у прочих).
+
+**Черновик КАК ТЕКСТ — ось (а) заказа, до редактора** (описательная: пре-рег Ф2 сузил решающее
+семейство до финалов, поэтому поправку она не несёт; знак «+» = черновик жильца лучше якорного):
+
+```
+D1 luna +0.12 · D2 dspro +1.81 · D3 gemini-fl +0.87 · D4 glm-4.7 −5.44 · D5 grok −0.44
+```
+
+Точная форма вердикта такова: **выше порога различимости 2.03 якорный черновик не проигрывает
+никому** — лучший из соперников, `deepseek-v4-pro`, опережает его на +1.81, то есть внутри шума.
+Ниже порога он проигрывает троим из пяти, и это надо читать как «не установлено», а не как
+«ничья по существу». Зато в обратную сторону разрыв реален: черновик `glm-4.7` хуже якорного на
+**−5.44** — больше любого перевеса в решающих таблицах Ф2 и Ф3.
+
+⚠ **И этот же −5.44 бьёт по посылке, выданной промтом.** Промт исходил из эксп-21 §16.2, что
+черновик двигает ~70% качества финала. Здесь черновик, отстающий на −5.44, даёт финал, отстающий
+всего на **−1.25** (E4): редактор съедает четыре пятых разрыва. Панель редакторов при этом
+разошлась ШИРЕ (от −2.12 до −4.75), чем панель черновиков (от −0.44 до −2.56). На этом материале
+доля черновика в качестве финала не 70%, а заметно меньше доли редактора — посылка не
+подтвердилась, и это вход для решения владельца, а не сноска.
+
+### 2. ЖИЛЕЦ РЕДАКТОРСКОЙ РОЛИ: роль впервые РАЗДЕЛИЛАСЬ — `deepseek-v4-pro` побеждает значимо
+
+Поверх ПОБАЙТНО одной якорной базы, семейство из четырёх, Холм по четырём:
+
+```
+R2 glm-5 −2.12 [−3.81, −0.75] Холм 0.0188 ✔
+R3 deepseek-v4-flash −2.54 [−4.00, −1.00] Холм 0.0188 ✔
+R4 grok-4.3 −4.75 [−6.12, −3.44] Холм 0.0001 ✔
+R0 vs F (КОНТРОЛЬ) +4.44 [+3.00, +6.12] Холм 0.0002 ✔
+```
+**Все три альтернативы значимо хуже боевого редактора.** О пороге различимости — точно, потому
+что порогов в паке ДВА и они дают разный ответ для одного арма:
+
+| порог | откуда | R2 −2.12 | R3 −2.54 | R4 −4.75 |
+|---|---|---|---|---|
+| **2.03** | пол Ф2; **пред-объявлен** для обеих фаз (§0-Ф3) | проходит на 0.09 | проходит | проходит |
+| 2.47 | собственный пол прохода p3 (снят позже) | **НЕ проходит** | проходит | проходит |
+
+По зафризенному критерию `glm-5` проходит — но запасом 0.09 при поле, чья оценка сама шумная.
+Честная форма: **`R3` и `R4` хуже боевого при любом пороге; `R2` — пограничный.**
+
+Эксп-21 на паре {dspro, glm-5} писал «движки неразличимы» (−0.69, ноль внутри интервала); здесь
+знак тот же, что и у эксп-21, но величина выросла до −2.12 и Холм пройден. ⇒ **Резерв D39.22 не
+опровергнут и не подтверждён:** единственный контраст, который его касается, — ровно тот, что
+стоит на границе. Формулировка «требует пере-рассмотрения» была бы сильнее данных; правильный
+статус — **под вопросом, решать не на этом замере**.
+
+### 3. ВТОРОЙ ПРОХОД ПОДТВЕРЖДЁН ДВАЖДЫ И УСИЛИЛСЯ
+
+Контроль «редактор поверх черновика» прошёл поправку в ОБЕИХ фазах на разных панелях:
+**+3.25** (Ф2, [+2.25, +4.19], Холм 0.0007) и **+4.44** (Ф3, [+3.00, +6.12], Холм 0.0002).
+У эксп-21 на контаминированной книге было +2.50 / +2.81. **Топология подтверждена на материале,
+которого модель не видела.**
+
+### 4. ⚠ НА ПАРЕ en ВЫВОД НЕ ПЕРЕНОСИТСЯ — контроль равен НУЛЮ
+
+`E0 vs D0` на английском исходнике = **+0.00** [−2.50, +2.33]: боевой редактор поверх якорного
+черновика не покупает НИЧЕГО, а финалы поверх других черновиков скорее лучше (+0.17…+2.17).
+
+⚠ **«Ни один контраст не проходит поправку» на en — свойство ДИЗАЙНА, а не данных, и как довод
+не годится.** При n=6 минимально достижимое p точного знакового теста = 2⁻⁵ = 0.03125; после Холма
+по шести контрастам это 0.1875 > 0.05. То есть en-ветка не могла дать значимость ни при каком
+результате — её мощности не хватало по построению, и объявлять её отрицательный итог «замером»
+было бы подгонкой под удобный вывод. Несущее здесь — не «незначимо», а **точечная оценка контроля
+ровно ноль при zh-контроле +3.25**, причём интервал [−2.50, +2.33] исключает лишь эффект ≥+2.5:
+эффект величиной ~+2 на en этими данными НЕ исключён.
+
+⇒ Выбор жильцов — решение ПАРЫ zh→ru. На другую пару его переносить нельзя; но и утверждать, что
+на en связка не работает, этот замер не даёт права — он даёт право сказать, что **перенос не
+показан**, и что en-ветку надо мерить отдельным паком с достаточным n.
+
+### 5. ЦЕНА СВЯЗКИ И ПРОЕКЦИЯ НА КНИГУ (1500 единиц, $/ед из `usage`), p50 И p95
+
+Промт заказал оба квантиля по единицам. p95 при n=16 — это максимум по выборке (ближайший ранг),
+и он важнее медианы для потолков: платить придётся по хвосту, а не по середине.
+
+```
+жилец связка p50 связка p95 книга p50 книга p95
+gemini-3.1-flash-lite 0.00881 0.01886 $13.21 $28.30
+deepseek-v4-flash 0.00897 0.01675 $13.45 $25.13
+gpt-5.6-luna 0.00921 0.02006 $13.81 $30.10
+glm-4.7 0.01271 0.02116 $19.06 $31.73
+deepseek-v4-pro 0.01321 0.02238 $19.81 $33.56
+grok-4.3 0.01536 0.02516 $23.04 $37.75
+```
+
+**Против БОЕВОЙ ПАРЫ,** как заказано промтом: боевая пара — это `deepseek-v4-flash` (черновик) +
+`deepseek-v4-pro` (редактор), то есть **первая строка с ценой $0.00897/ед → $13.45 за книгу по
+медиане и $25.13 по p95**. Ни один соперник её не удешевляет: ближайший, `gemini-3.1-flash-lite`,
+дешевле на 1.8% по медиане ($13.21) и **дороже на 12.6% по хвосту** ($28.30). ⇒ По цене менять
+жильца не на что — как и по судье.
+
+⚠ **Хвост почти вдвое выше медианы у всех шести** (p95/p50 = 1.7–2.2×), и по хвосту порядок
+жильцов меняется: рекомендованный `deepseek-v4-flash` по медиане второй, а по p95 — **самый
+дешёвый**. Потолки на книгу надо ставить по p95, иначе каждая длинная глава будет пробивать смету.
+⚠ **DeepSeek объявил на прайс-странице 08.08 повышение цен** («significant increase expected») —
+проекция едет с этой оговоркой и подлежит пере-счёту при смене прайса.
+
+### 6. ⛔ ОБЯЗАТЕЛЬНЫЙ КАНДИДАТ ПРОМТА НЕ ОТРАБОТАЛ НА ПРОВОДЕ
+
+`gemini-3.1-pro-preview`: в скрине **четыре отказа HTTP 503 — две редакторские клетки и две
+переводческие**; переводческие взялись с ретрая, редакторские не взялись вовсе, поэтому
+редакторской цены у модели нет, а скрин-вердикт ПРОВАЛ вынесен по переводческой ($0.14282 против
+порога $0.02). В Ф3 — **1 клетка из 16**, один вызов держал замок 74.5 минуты без ответа и без
+ошибки. Цена состоявшейся клетки $0.14702 при **11 504 тарифицируемых выходных токенах на 2 532
+видимых**: тарифицируется в 4.5 раза больше, чем видно, то есть скрытая часть — 8 972 токена,
+втрое больше самого ответа; видно это только в `total_tokens`. ⇒ **Гипотеза эксп-04 «gemini-pro —
+аутлаер прозы» НЕ проверена и остаётся открытой** — по операбельности, а не по качеству.
+
+### 7. ЧТО ЕЩЁ ИЗМЕРЕНО (детерминированно, судьи не требует)
+
+* **Эхо-мина — свойство МАТЕРИАЛА:** `deepseek-v4-flash` дал **6%** ре-генов (1/16) против
+ 31–38% на 蛊真人; `gemini-3.1-flash-lite` — 12% и **одна единица не покупается вовсе**
+ (эхо 3/3 попыток). Гипотеза 5.3 эксп-21 подтверждена вторым замером.
+* **Редактор чистит письменность и теряет канон.** Утечка ханьцзы в черновиках luna 75 и
+ glm-4.7 53 → после редактора 2 и 0. Но **покрытие канона падает у ВСЕХ шести жильцов**
+ (−0.027…−0.057) несмотря на закон-блок; у победителя Ф3 оно худшее в панели (0.882 против
+ 0.933 у glm-5 и 0.909 у самого черновика).
+* **`deepseek-v4-flash` в роли редактора однажды вернул черновик побайтно без правок** (1 клетка
+ из 62) — класс «no-op редактора».
+* ⚠ **`deepseek-v4-flash` в роли редактора Ф3 сжёг бюджет вхолостую на 3 клетках из 16.** Ровно
+ 16 000 из 16 000 выходных токенов ушли в размышление, `content` пуст, заплачено $0.01529. Это
+ ровно класс Г6, которым пак дисквалифицировал `kimi-k2.6`, — и он воспроизвёлся на модели,
+ ПРОШЕДШЕЙ скрин, только в другой роли. Отсюда `R3 n=13`: контраст −2.54 посчитан по 13 удачным
+ клеткам, то есть по благоприятному для дешёвого редактора подмножеству (вывод от этого не
+ падает — он и так против дешёвого, — но читателю это сказано явно). **Провод-факт для quirks:**
+ дешёвый режим DeepSeek в РЕДАКТОРСКОЙ роли уходит в отказ по исчерпанию бюджета размышления,
+ чего в переводческой роли на тех же единицах не случилось ни разу.
+* **Довесок `D_/A` на сырье эксп-21:** −1.56 [−2.88, −0.25], p=0.0526 — **и это число до
+ позиционной поправки.** Промт требует наклон замерить и вычесть; замерено по всем четырём
+ проходам: `p2zh` +0.078, `p2en` +0.037, `p3` −0.005 — ничтожны, ни один вердикт не двигают.
+ А в `dva` наклон **+0.469 ошибки на шаг метки**, и поправка даёт **−1.65, p=0.0356**, то есть
+ переводит контраст через 0.05.
+ ⚠ Вердикт я на этом НЕ меняю. Наклон в `dva` снят на трёх метках (n=48) — оценка хрупкая, а
+ решение, зависящее от того, снята поправка или нет, по определению не устойчиво. Честный статус
+ контраста «однопроходка против связки»: **знак воспроизведён третьим независимым замером
+ (−1.56 сырой, −1.65 с поправкой), значимость — на самой границе и от способа счёта зависит.**
+ «НЕ УСТАНОВЛЕНО» остаётся, но теперь с указанием, что оно держится на границе, а не с запасом.
+
+### 8. ДЕНЬГИ И ДИСЦИПЛИНА
+
+```
+Ф0 $0.008153 / $0.40 Ф2 $1.681912 / $2.00
+Ф1 $1.087758 / $1.05 ⛔ ПРОБОЙ Ф3 $0.631920 / $3.00
+ПАК $3.409743 / $6.50
+```
+⛔ **Потолок Ф1 пробит на $0.0378.** Против поднятого потолка $1.05 это 3.6%, **против исходного
+потолка промта $1.00 — 8.8%**; вторая цифра честнее как мера промаха планирования. Механизм:
+проекционный гард брал p95 УЖЕ купленных вызовов, а `gemini-3.1-pro-preview` оказался в семь раз
+дороже любого предшественника. Починено (ожидание считается по ЭТОЙ модели из прайса), заведена
+регрессия. Потолки Ф1 ($1.00→$1.05), Ф3 ($1.60→$3.00) и пакетный ($5.00→$6.50) подняты **словом
+владельца 08.08, объявлено ДО трат** (пункты 1–2 §9 — до подтверждения не ратифицированы).
+
+⚠ **Подъём потолков Ф3 и пака НЕ ПОНАДОБИЛСЯ.** Основанием была смета Ф3 $2.91, из которых $2.43
+на `gemini-3.1-pro-preview`; модель отдала одну клетку, и Ф3 стоила $0.631920 — **внутри
+ИСХОДНОГО потолка $1.60**, а пак $3.409743 — внутри исходных $5.00. Дисциплина ниже печатается
+против поднятых границ, но мерить себя надо по тем, что были до просьбы: по ним пак чист везде,
+кроме Ф1.
+
+⚠ **Неопределённость расхода Ф2 — граница пере-считана и она ХУЖЕ прежде объявленной.** Часть
+стадии шла двумя наборами параллельных процессов, а проверка «файла ещё нет» была неатомарной:
+клетка могла быть куплена дважды, и вторая оплата в леджер не попадает (на диске одна запись).
+Прежде здесь стояло «≤66 клеток × $0.005 ≈ $0.33» — **это число из артефактов не выводится**
+(откуда 66 — нигде не записано, а $0.005 ниже фактической цены 124 клеток Ф2), и я его снимаю.
+Выводимая из сырья граница одна: атомарный замок заведён в 20:43:25, до него куплено **176 клеток
+Ф2 на $0.846103**, и в худшем случае каждая из них оплачена дважды.
+
+⇒ **Верхняя граница невозвратной пере-оплаты: ≤$0.846103.** Если бы она реализовалась целиком,
+Ф2 стоила бы $2.53 и **пробила бы свой потолок $2.00** — то есть чистота Ф2 в таблице выше верна
+для записанного расхода и НЕ доказана для фактического. Реальная величина почти наверняка много
+меньше (гонка бьёт лишь когда два воркера берут одну и ту же клетку в одну и ту же секунду), но
+величины этой не существует ни в каком артефакте, и подменять границу правдоподобной оценкой —
+ровно то, чего гейт обязан не допускать. Класс закрыт атомарным замком; проверить фактическое
+можно только по счетам провайдеров.
+
+⚠ **Оплачено за ПУСТОЙ выход: 7 вызовов на $0.21726 — 6.4% пака.** Три клетки `kimi-k2.6` в
+скрине, три редакторские клетки `deepseek-v4-flash` в Ф3 (§7) и один черновик. Это не накладные
+расходы, а прямая потеря на отказном режиме размышления.
+
+⚠ **Ф2 стоила $1.681912 против собственной сметы $1.4533 (+15.7%).** В потолок влезла, поэтому
+согласия не требовалось, но расхождение сметы с фактом — тот же класс, что и сверка прогнозов,
+и молчать о нём нельзя. Источник — ре-гены эхо-мины и повторные клетки, которых смета не закладывала.
+
+**Агент-запусков: 40 из 60.** Из них 6 списаны на аннулированный проход en (см. §12).
+
+## §16 ВНЕШНИЙ СУДЬЯ SOL — ПАКЕТ (а) ОТСУЖЕН, АРБИТРАЖ НЕ СОСТОЯЛСЯ
+
+20 пакетов, обе половины каждой зеркальной пары, прогнаны владельцем 09.08 двумя сессиями.
+Границы задания Sol не нарушал, формат выдержал везде.
+
+**Что уцелело.** ДЕКОЙ пойман **2/2 в обоих порядках**: модель другой семьи независимо отличает
+подсаженную деградацию от чистого текста на этом материале. Позиционное смещение замерено и
+велико — первый предъявленный вариант получает в среднем на **4.61** ошибки меньше, причём
+стабильно в обоих заходах (−5.00 и −4.22). Это внешнее подтверждение того, что позиционный
+контроль в абсолютном риге не формальность.
+
+**Почему арбитраж не состоялся — три причины, все на стороне замысла, не Sol.**
+
+1. **Недомощность по построению.** `N_UNITS = 3`: три единицы на контраст. Расхождение платных
+ судей, ради которого пакет существует (luna +0.84 против gemini +3.03), тремя точками не
+ разводится ни при какой калибровке. Константа стояла в коде до того, как пакеты ушли владельцу.
+2. **Дрейф абсолютной шкалы между сессиями ×2.6.** Медиана ошибок на вариант: заход 1 — 23.5
+ (разброс 5–44), заход 2 — 9.0 (2–21). Один и тот же текст получает 43 ошибки в первом заходе и
+ 6 во втором.
+3. **Зеркальные пары разведены по разным сессиям** — решение полигона, принятое чтобы Sol не узнал
+ тексты со второго раза. Из-за этого усреднение двух порядков, которое должно снимать
+ позиционное смещение, складывало число из одной шкалы с числом из другой: знак совпал лишь в
+ **3 парах из 9**. Напечатанные ригом «Sol: −7.50 / −5.33 / +1.33» этим и порождены; как третье
+ мнение рядом с luna и gemini их подавать нельзя, и здесь они не подаются.
+
+**Сравнение со своим ригом — в пользу структуры, а не судей.** Мои судьи дрейфуют почти так же:
+медианы 8 сессий эксп-23 — от 4.5 до 9.0, отношение 2.0×. Разница в том, что в абсолютном риге все
+армы единицы судятся В ОДНОМ контексте, поэтому множитель шкалы стоит в обоих слагаемых перевеса и
+из разности уходит; у Sol разность считалась между двумя головами. ⇒ **Требование «все армы
+единицы — в одном пакете одной сессии» переходит из удобства в норму рига.**
+
+**Чинится не промтом.** Нужен якорь шкалы внутри КАЖДОГО пакета (откалиброванный фрагмент с
+известным числом ошибок) плюс достаточное n. Это новый пак по заказу владельца, а не пере-запуск:
+пакет (б) в нынешнем виде (`N_UNITS_B = 3`) ляжет в ту же яму и не выпускается.
+
+**Диагноз подтверждён исполнением на соседнем паке.** Тому же внешнему судье отдали задания
+эксп-23 — не пакеты этой конструкции, а обычные файлы абсолютного рига, где все армы единицы лежат
+в ОДНОМ задании. Результат: 32 из 32 приняты, декой пойман 32/32, вердикт совпал с моими судьями
+(эксп-23 §12в). ⇒ Дефект был не в судье и не в канале, а в конструкции пакета: разнесение
+зеркальных пар по сессиям. Требование «все армы единицы — в одном задании» подтверждено с двух
+сторон: нарушив его, замер разваливается; соблюдя — работает на том же судье.
+
+**Строка 150 бэклога:** пакет (а) исполнен и дал отрицательный результат по своей задаче; пакет
+(б) НЕ запускать — вместо него внешний контур строить на заданиях абсолютного рига, как в эксп-23.
+
+### 9. CONFIRM/DENY ВЛАДЕЛЬЦУ (известно только со слов сессии, до подтверждения не ратифицируется)
+
+⚠ Пункты 9–11 добавлены 09.08 по итогам аудита закрытия заказа — того, что искал обязательства,
+закрытые артефактом вместо действия. Пункт 7 в том же аудите переписан: он скрывал суть.
+
+1. Подъём потолка Ф1 до $1.05 (08.08). *Пробой случился всё равно: $1.087758.*
+2. Подъём потолков Ф3 до $3.00 и пакетного до $6.50 (08.08), **с отклонением варианта «gemini-pro
+ на подмножестве единиц»**.
+ ⚠ **Сессия применила отклонённый вариант.** `editors.py` ограничивает медленную модель
+ восемью единицами (`SLOW_UNITS = 8`), то есть ровно подмножеством, от которого владелец
+ отказался в пользу подъёма потолка. Практического следа это не оставило — модель отдала 1
+ клетку из-за провода, а не из-за лимита, — но решение было моё, а не владельца, и подаваться
+ как согласованное оно не должно.
+3. Разрешение на удаление вложенности `~/books/books/` и перенос сырья наверх (08.08).
+ *На тех же симлинках стоит воспроизводимость эксп-21.*
+4. **Использование живой сессии владельца (куки) для доступа к площадке при сборе среза.** Мера
+ была предложена самим владельцем, но санкция на неё известна только со слов сессии, и у неё
+ есть ToS-сторона, которую подтверждать владельцу.
+5. **Гейт прав снял две главы** (верхний дециль эротических маркеров) ДО отбора единиц. Это
+ решение сессии по договорам провайдеров, и оно сужает продуктовый класс материала — см. §11.
+6. **Кредиты xAI.** Промт фиксировал их исчерпанными 07.08 с пометкой «вопрос у владельца»;
+ `grok-4.3` в паке скринился и покупался во всех фазах, то есть пополнение состоялось.
+ Подтвердить, что оно было осознанным.
+7. **Sol ВЕРНУЛСЯ 09.08 — и для арбитража оказался непригоден; отдельно: его не просили сутки.**
+ Владелец прогнал все 20 пакетов (а) двумя сессиями. Результат по существу — в §16. Прежняя
+ редакция этого пункта («выпущен и не вернулся») скрывала главное: пакеты пролежали в
+ `~/books/tenant-panel/sol*/` сутки, каталоги `answers/` пусты, а просьба владельцу прозвучала
+ только 09.08, после его прямого вопроса «где я это должен был запустить». Промт (стр.33) требовал
+ отдать paste-ready пакеты ему в руки; сессия сочла обязательство закрытым по факту генерации
+ файлов. ⚠ Поправка фазы Д (найдена приёмкой другого модельного семейства): фраза «без Sol у обоих
+ паков нет ни одного контура вне одной семьи» здесь стояла и была неверна — она против
+ §12в эксп-23, где подпись судьи вне Claude по проходу `border` напечатана числами.
+ Верно так: контур ПОСТРОЕН и работает, но на заданиях абсолютного рига и по контрастам
+ ЧУЖОГО пака; контрасты ЭТОГО пака внешней подписи не имеют (§11).
+
+9. **Оплаченная en-ось шла БЕЗ обязательного банк-закона** (D39.104, промт стр.30 и 55 — пункт
+ ОБЯЗАТЕЛЕН, для таких «путь один: пинг»). 72 клетки куплены без блоков; причина не в сторожах
+ кода, а в том, что банк среза собран только по zh (22 термина, все китайские), поэтому на en
+ блок вышел бы пустым в любом случае. Отступление объявлено во фризе Ф2 ДО покупок, но пинга не
+ было и выбор «собрать en-банк или принять ось описательной» владельцу до трат не выносился.
+ ⚠ Сводная таблица §13а называет этот пункт «исполнено» — это неверно и правится: на оси en не
+ исполнено. Решение владельца: собирать en-банк и пере-купить 72 клетки — или объявить ось
+ описательной.
+
+10. **Пробой потолка Ф1 не остановил пак.** $1.087758 против поднятого $1.05: промт (стр.5)
+ требовал «не влезает → СТОП и пинг ДО покупок». Число названо владельцу в день пробоя и
+ напечатано в отчёте со знаком ⛔, но СТОПа не было — следующая фаза зафризена и покупки пошли
+ дальше. Вопрос: остаются ли $1.05 и пакетные $6.50 нормой в `money.py` (их унаследовал пак 23)
+ или откатываются к $1.00/$5.00 как мерилу дисциплины.
+
+11. **Вахта dspro исполнена одним обращением и ПОСЛЕ покупок.** Промт (стр.31) требовал сверку
+ вендор-changelog дважды — в Ф0 и перед Ф3. Фактически: одно обращение к changelog DeepSeek
+ после фризов Ф0/Ф3 и после покупок Ф3, снимок страницы не сохранён (в `prices/` лежат только
+ прайс-листы). Сам вывод «смены маппинга после фриза Ф2 не было» проверен и держится, условие
+ «сменился → стоп и пинг» не наступало; неисполненной осталась ФОРМА вахты, а не её итог.
+12. **Отказ гнать строку 153** (уравненный мандат `D`/`D_`) и **отказ от второй базы Ф3** — обе
+ опции промта сняты решением сессии, не владельца.
+
+⚠ **Поправка фазы Д (чек-лист приёмки №16 п.10): нумерация этой секции была сломана.** Ряд шёл
+1–7, 9, 10, 11, 8 — пункт «отказ гнать строку 153» стоял под номером 8 ПОСЛЕ одиннадцатого, а
+пункта 8 в положенном месте не было вовсе. Причина видна по датам: пункты 9–11 дописаны 09.08
+аудитом закрытия заказа и вставлены перед хвостом. Пункт пере-нумерован в 12 (текст не тронут),
+три ссылки в §13а и §14 на прежний номер поправлены на «§9 п.12».
+
+### 9а. ⚠ ГЕЙТ ToS Z.AI — ГЕЙТ РЕШЕНИЯ ВЛАДЕЛЬЦА, НЕ ЗАМЕРА
+
+Промт требует пометить это в ИТОГе, и вот пометка. **Замер `glm-5` и `glm-4.7` легален** — якорь
+покупается, и все числа пака по ним получены правомерно. Но **ПРОД-выбор жильца Z.AI — отдельный
+гейт**: маршрутизация по договорам провайдеров идёт ПРАВОМ, а не поведением модели, и класс
+`sexually-explicit` для `zai` объявлен FORBIDDEN. Для книги с чувствительным содержимым это значит,
+что `glm-5`/`glm-4.7` нельзя ставить жильцом на такой материал, даже если замер их пропустит.
+
+На выводы пака это не влияет ни в одну сторону: `glm-4.7` в черновой роли и так не рекомендован,
+а `glm-5` в редакторской — пограничный (§2). Но если владелец будет выбирать жильца по цене или
+по будущим замерам, гейт прав срабатывает РАНЬШЕ качества. Лейбл-вопросы — не задача полигона.
+
+### 10. ДИСПОЗИЦИИ СТРОК БЭКЛОГА
+
+* **55** (эмпирика на претрейн-классике предварительна) — **ЗАКРЫТА**. Неконтаминированный
+ zh-срез добыт и замерен (0/5 на двух моделях при живом контроле); главные выводы топологии
+ на нём воспроизвелись и усилились. Остаток строки исчерпан.
+* **150** (Sol-арбитраж) — **ПАКЕТ (а) ПРОГНАН ВЛАДЕЛЬЦЕМ 09.08, АРБИТРАЖ НЕ СОСТОЯЛСЯ; ПАКЕТ (б)
+ НЕ ЗАПУСКАТЬ.** ⚠ Поправка фазы Д (п.3): прежняя редакция этой строки гласила «оба пакета
+ выпущены, ждут владельца» и обрывалась на середине фразы — статус устарел на день и противоречил
+ §16 того же отчёта.
+ **(а)** 20 файлов в `~/books/tenant-panel/sol/` + инструкция; контрасты `A/F`, `B/F`, `A_law/A`
+ отобраны по фактическому расхождению судей эксп-21. Прогнан владельцем двумя сессиями. Итог —
+ §16: границы задания Sol не нарушал, декой поймал 2/2 в обоих порядках, позиционное смещение
+ замерил (−4.61), но арбитраж развалился по КОНСТРУКЦИИ пакета (`N_UNITS = 3`, дрейф шкалы между
+ сессиями ×2.6, зеркальные пары разнесены по разным сессиям — знак совпал в 3 парах из 9).
+ **(б)** заказан промтом «по готовности Ф2/Ф3» — обе фазы готовы, поэтому был выпущен: **14 файлов**
+ в `~/books/tenant-panel/sol-b/`, слепые пары финалистов, оба порядка, декой, ключ отдельно.
+ Контрасты: **`R0` против `R2`** (рекомендованный редактор против пограничного — на нём висит
+ вопрос о Резерве D39.22) и **`E0` против `E2`** (крупнейший перевес Ф2). Единицы отобраны те,
+ где внутрисемейный судья был увереннее всего. Семейный запрет соблюдён и проверяется кодом:
+ клеток OpenAI-моделей в пакете нет (`sol.py::_no_openai`). **Владельцу НЕ отдавать в этом виде:**
+ та же конструкция и тот же `N_UNITS_B = 3` уложат его в ту же яму.
+ ⇒ **Рабочая замена найдена и проверена на соседнем паке:** внешний контур строится на заданиях
+ АБСОЛЮТНОГО РИГА, где все армы единицы лежат в одном задании. Так эксп-23 получил подпись судьи
+ вне Claude по проходу `border` — 32 из 32 приняты, декой 32/32, вердикт совпал (эксп-23 §12в).
+ Контрасты ЭТОГО пака внешней подписи по-прежнему не имеют.
+* **153** (чистый контраст мандата `D`/`D_`) — **НЕ ГНАЛСЯ.** Опция промта; бюджет ушёл в
+ обязательные части, а уравненные промты требуют отдельного пре-рега. Остаётся открытой.
+* **151** (отказной режим связки) — **УСИЛЕНА**: на новом материале воспроизведён случай, когда
+ годного черновика нет ВОВСЕ (`gemini-3.1-flash-lite`, эхо 3/3 попыток на одной единице).
+* **152** (слипание абзацев — слепое пятно детерминированного контура) — **УСИЛЕНА**: судья и
+ батарея дали РАЗНЫЙ порядок армов в Ф3 (победитель по судье имеет худшее покрытие канона).
+
+### 11. ЧТО НЕ ПЕРЕНОСИМО И ПОЧЕМУ
+
+* **Величины на пару en** — контроль редактора там ноль (§4).
+* **Величины на другую книгу** — эхо-мина изменилась с 38% до 6% при смене материала; всё, что
+ зависит от свойств текста, обязано пере-меряться.
+* **Вывод про `gemini-3.1-pro`** — его нет; есть вывод про провод.
+* **Абсолютные уровни между проходами** — пороги строгости судейских популяций различаются;
+ сравнивать можно только внутриединичные разности внутри одного прохода.
+* ⚠ **ВСЕ судейские числа пака сняты ОДНОЙ модельной семьёй.** Сорок судейских сессий — это сорок
+ независимых контекстов одной и той же модели, а не независимые популяции. Проход `dva` назван
+ в теле «третьей независимой судейской популяцией» — независимы там СЕССИИ, семья одна.
+ Структурная защита от «судья тянет за своих» в паке есть и она сработала: в ростере (`roster.py`)
+ нет ни одной модели семьи судьи, то есть подсудимых своей семьи у него нет. Но защиты от общего
+ для семьи ПРЕДПОЧТЕНИЯ (какая русская проза считается хорошей) нет никакой, а эксп-21 показал,
+ что смена семьи судьи двигает контраст на 2.19 балла (D39.117 п.5б). Заголовочный вывод пака
+ держится на согласии однородных судей. ⚠ **Поправка фазы Д (п.3): здесь стояло «внешний контур —
+ это Sol-пакет, и он НЕ ВЕРНУЛСЯ».** Он вернулся 09.08 и оказался непригоден по конструкции
+ пакета (§16), а рабочий внешний контур построен на заданиях абсолютного рига и покрывает ПРОХОД
+ ЧУЖОГО ПАКА (эксп-23 `border`), а не контрасты этого. ⇒ Все судейские величины ЭТОГО пака
+ по-прежнему следует читать как «так их видит одна модельная семья», а не как «так есть» — но по
+ причине «внешний судья наших контрастов не читал», а не «внешний судья не ответил».
+* ⚠ **Ось отказов замерена на материале, из которого убран самый провокативный класс.** Гейт прав
+ снял две главы верхнего дециля эротических маркеров ДО отбора единиц (§0.2), после чего гейт Г3
+ «отказ на чувствительном фрагменте» не поймал никого. Это ожидаемо и ничего не говорит о том,
+ как жильцы ведут себя на чувствительном материале, — а именно он и есть рабочий случай продукта
+ (цель 1 канона владельца). Ноль отказов здесь — свойство выборки, не моделей.
+
+---
---
@@ -276,10 +690,18 @@ grok-4.3 прошёл 0.00984 0.01452 0 20.9
kimi-k2.6 ПРОВАЛ 0.05661 0.06773 0 100.0% Г1 (3 клетки) · Г5 обе роли · Г6 пустой выход
mistral-large-latest ПРОВАЛ 0.02016 0.02661 0 0.0% Г5 цена translator $0.0202 > $0.02
gemini-3.6-flash ПРОВАЛ 0.07320 0.06354 0 0.0% Г5 цена обеих ролей
+gemini-3.1-pro-preview ПРОВАЛ 0.14282 — 0 0.0% Г5 цена translator $0.1428 > $0.02
```
-**Скрин убил 3 из 11** — против 2 из 12 у эксп-21, и убил по СОДЕРЖАТЕЛЬНЫМ порогам, а не по
-удобству состава.
+**Скрин убил 4 из 11** — против 2 из 12 у эксп-21, и убил по СОДЕРЖАТЕЛЬНЫМ порогам, а не по
+удобству состава. Прошли 7: якорь и шесть кандидатов черновой роли.
+
+⚠ **`gemini-3.1-pro-preview` — вердикт на НЕПОЛНОМ замере.** Его строка стоит на 2 клетках вместо
+4: обе редакторские ушли в HTTP 503 (§2.3), поэтому редакторской цены у него нет вовсе. Вердикт
+ПРОВАЛ выносится по переводческой цене, которая выше порога в семь раз, — разрыв такой, что двумя
+недостающими клетками он не закрывается. **Это не отменяет его участия в Ф3:** обязательный
+кандидат промта берётся туда безусловно, мимо скрина (§6), и именно поэтому потолок Ф3 поднимался
+владельцем до покупок.
⚠ **`mistral-large-latest` провалился с запасом в 1%** ($0.02016 против порога $0.02). Порог
объявлен до замера и не двигается, но честная форма вердикта — «на границе», а не «дорог».
@@ -383,3 +805,588 @@ E0 vs D0 — КОНТРОЛЬ: п
**АГЕНТ-ЗАПУСКИ Ф2:** 16 (zh, по одной единице на сессию: 6 черновиков + 6 редактур + декой +
пара пола = 14 текстов) + 3 (en, по две единицы: 6 черновиков + 6 редактур + декой = 13 текстов).
+
+---
+
+## §3 ОБРАТНАЯ СВЯЗЬ ПО ОРГАНИЗАЦИИ ЗОНЫ `eval/` (сайд-задание владельца)
+
+Пишется как наблюдение исполнителя, который в этот код только что въехал и на каждой мине
+постоял. Приоритет — по цене ошибки, а не по красоте.
+
+**1. Две мины импорта, на которые наступит каждый следующий пак.**
+`probe.py` существует И в `editor_contract/`, И в `editor_harness/`; какой подхватится — решает
+ПОРЯДОК `sys.path.insert` в вызывающем файле. `bakeoff.py` вставляет четыре каталога и получает
+`editor_harness/probe.py`, хотя по тексту импорта это неочевидно. Переименование любой из папок
+порвёт чужой пак молча. Дёшево лечится: `__init__.py` + импорт пакетами (`from editor_harness
+import probe`), либо хотя бы `assert probe.__file__.endswith(...)` в шапке.
+
+**2. Сырьё привязано к пути константой, и путь уже ломался.** Все паки пишут в
+`Path.home()/"books"/<пак>`; на этом стенде каталог оказался вложен на уровень глубже
+(`~/books/books/role-topology`), и харнесс эксп-21 видел ПУСТОТУ вместо 2 055 файлов — печатал
+«голосов нет» и завершался с кодом 0. То есть отчёт мог быть «пере-снят из сырья» на нулевом
+сырье. Лечение: один модуль `paths.py` с проверкой «каталог непустой, иначе падать громко».
+(На время пака поставлены симлинки `~/books/role-topology` и `~/books/gu-zhenren/bank-arbitration`
+— это костыль, не решение.)
+
+**3. Прайс-таблица дублируется по пакам.** `role_topology/prices.py` и `tenant_panel/roster.py`
+знают разные множества моделей и РАЗНЫЕ цены (у Gemini кэш-цена в первой равна входной, а вендор
+публикует её в 8–10 раз ниже). Касса эксп-21 считает цену своей таблицей — эксп-22 упёрся в
+`KeyError` на середине скрина. Прайс — это данные с датой и источником; ему место в одном файле
+на зону, а не в каждом паке.
+
+**4. Верхний уровень `eval/` смешивает эпохи.** Тридцать свободных `.py` от экспов 01–11 лежат
+рядом с папками `exp12/`…`exp16/`, `promptlang/`, `role_topology/`. Решение владельца D38.2
+(«код → логические папки») исполнено ЧАСТИЧНО: старое не переехало. Пока это не мешает, но карта
+в README устаревала именно из-за этого.
+
+**5. README зоны отставал на четыре пака.** Карта покрывала exp01–16 и `promptlang/`, но не
+`bank_arbitration/`, `editor_contract/`, `editor_harness/`, `role_topology/` — то есть ВСЕ
+действующие харнессы. Новая сессия не могла найти по README ни судейский риг, ни кассу, ни
+батарею. **Исправлено этим паком:** в `eval/README.md` добавлена таблица «Действующие харнессы
+паков 18–22» и две мины импорта из п.1–2.
+
+**6. Что НЕ надо трогать.** `absjudge.py`, `battery.py`, `buy.py`, `editor_wire_probe.py`,
+`inject_probe.py` — это фундамент, на котором стоят числа трёх паков. Их стоит вынести в общий
+слой и покрыть тестами, но не переписывать: цена регрессии здесь измеряется деньгами прогонов.
+
+**Вывод по рефакторингу.** Крупный не нужен. Нужны три дешёвых шага в порядке цены ошибки:
+`paths.py` с громким падением (п.2) · единый `prices.py` на зону с датой и источником (п.3) ·
+пакетные импорты вместо `sys.path`-цепочек (п.1). Переезд старых скриптов в папки (п.4) — косметика,
+делать по случаю.
+
+---
+
+## §0-Ф3 ПРЕ-РЕГ ФАЗЫ 3
+
+⚠ **Поправка о статусе этой секции (внесена приёмкой).** Прежде заголовок гласил «зафризен своим
+коммитом ДО покупок Ф3». Это неверно и проверяется одной командой: `git log -S"ПРЕ-РЕГ ФАЗЫ 3" --
+docs/experiments/22-tenant-panel.md` пуст — текста секции нет ни в одном коммите, он живёт только
+в рабочем дереве. Заморожен КОДОМ: состав панели, семейство контрастов и изоляция уехали во фриз
+`56b34d4` (21:12), первая покупка Ф3 — 21:13:38, и именно код определяет, что было куплено и как
+судилось. Существо пре-рега от этого не страдает, но самоописание секции было ложным, и
+оставлять его нельзя: «зафризено» — проверяемое утверждение, а не риторика.
+
+**База — ОДНА и замороженная:** боевой черновик `deepseek-v4-flash` из Ф2, тот самый, что прошёл
+гейт эха. Все редакторы правят побайтно один текст, поэтому контраст между ними чист от разницы
+черновиков — конфаунда, который эксп-21 назвал носителем ~70% эффекта. Изоляция клеток
+(различие ТОЛЬКО в модели) проверяется `editors.py --isolation` сборкой сообщений ДО покупок.
+
+**Состав панели по объявленному ЗДЕСЬ правилу:**
+
+```
+R0 deepseek-v4-pro боевой редактор — база всех контрастов; клетка КУПЛЕНА В Ф2
+ (`tp2-edit` над якорным черновиком) и заново не покупается
+R1 gemini-3.1-pro-preview ОБЯЗАТЕЛЬНЫЙ кандидат промта (гипотеза эксп-04 «аутлаер прозы»)
+R2 glm-5 ратифицированный резерв D39.22 = арм B эксп-21 — ПРЕЕМСТВЕННОСТЬ:
+ только он позволяет пере-снять `A_law/B` на чистом материале
+R3 deepseek-v4-flash самый ДЕШЁВЫЙ прошедший редактор — «хватает ли дешёвого»
+R4 grok-4.3 самый ДОРОГОЙ прошедший — «покупает ли дорогой качество»
+```
+
+**Вторая база (лучший черновик Ф2) — ОПЦИЯ промта, НЕ берётся.** Основание: смета Ф3 и без неё
+упирается в потолок (см. ниже), а вопрос «меняется ли выбор редактора при другой базе» —
+интеракция, на которую 16 единиц мощности не дают. Решение объявлено ДО покупок, а не после.
+
+**СЕМЕЙСТВО КОНТРАСТОВ Ф3, впечатывается в ключ ДО первого ответа:**
+
+```
+R1 vs R0 · R2 vs R0 · R3 vs R0 · R4 vs R0 — каждый редактор против боевого над ТОЙ ЖЕ базой
+R0 vs F — КОНТРОЛЬ: покупает ли боевой редактор над базой
+```
+Поправка Холма — по пяти.
+
+**КРИТЕРИИ — те же, что в Ф2, и на том же поле:** «X бьёт Y» при перевесе больше шумового пола
+(пол измерен в Ф2 двумя end-to-end прогонами якорного арма и применяется к обеим фазам) И
+`p` Холма < 0.05. Ниже пола — вердикт «ниже порога различимости» с числом. Ничья ⇒ рекомендация
+= самый дешёвый жилец (D39.117).
+
+**СМЕТА Ф3 по ЗАМЕРЕННЫМ ценам** (не по прайсу): R1 $0.152×16 = $2.43 · R2 $0.01303×16 = $0.21 ·
+R3 $0.00264×16 = $0.04 · R4 $0.01452×16 = $0.23 · R0 = $0 (из Ф2) ⇒ **$2.91 при потолке $3.00**.
+Потолок Ф3 поднят с $1.60 до $3.00 и пакетный с $5.00 до $6.50 **словом владельца 08.08**,
+объявлено ДО покупок; причина — замер скрина: `gemini-3.1-pro-preview` тарифицирует 11 144–12 010
+выходных токенов на ответ в 2 508 (скрытое размышление видно ТОЛЬКО в `total_tokens`).
+
+**ДОВЕСОК на купленном сырье эксп-21 (генерации НЕ покупаются):** прямой контраст `D_` против `A`
+агент-голосами, проход `dva`, семейство из одного контраста. Эксп-21 дал −2.00 (p=0.0157, Холма
+не проходит) на пуле двух проходов; здесь он пере-снимается третьей независимой судейской
+популяцией.
+
+**АГЕНТ-ЗАПУСКИ Ф3:** 8 (по две единицы на сессию: 5 редакторов + база + декой + пара пола ≈ 9
+текстов) + 4 на довесок `dva` (по четыре единицы: 2 текста + декой).
+
+---
+
+## §4 Ф2 — ПАНЕЛЬ ЧЕРНОВИКОВ: РЕЗУЛЬТАТЫ
+
+### 4.0 КОНТРОЛИ САМОГО РИГА (читать ДО боевых чисел)
+
+```
+проход ДЕКОЙ (посаженная деградация) ПОЛ (две генерации одного арма, истина = 0)
+p2zh −4.44 · поймано 16/16 ✔ +0.19 [−1.19, +1.56] n=16 · ЧЕСТЕН
+p2en −3.50 · поймано 6/6 ✔ не ставился (пол — заказ zh)
+```
+`sd` пола по единицам **2.90**, стандартная ошибка среднего **0.72** ⇒ **различимый средний эффект
+≈ 2.03 ошибки** при 80% мощности. Это и есть порог, к которому прикладываются все перевесы Ф2/Ф3.
+Побайтных близнецов в пачках нет.
+
+⚠ **Граница декоя, объявляю.** Посадки книго-независимы (заменены эксп-22), но у них узнаваемый
+«почерк»: химера на `-нительние`, `сорок` вместо числительного, снятое отрицание, висячая прямая
+кавычка. Судьи, получавшие 2–4 задания в одной сессии, называли метку декоя в каждом. Контроль от
+этого не рушится — он меряет, ВИДИТ ли судья посаженную порчу, — но **величина −4.44 завышена**
+и как оценка чувствительности не читается.
+
+### 4.1 Судейство zh (16 единиц, семейство впечатано в ключ ДО ответов, Холм по 6)
+
+```
+контраст ед. перевес 95% ДИ p p Холма
+E1 vs E0 16 −0.44 [−2.00, +1.25] 0.6711 0.6711 финал поверх черновика luna
+E2 vs E0 16 −2.56 [−5.62, +0.06] 0.1058 0.3452 …поверх черновика deepseek-v4-pro
+E3 vs E0 15 −1.87 [−3.60, +0.00] 0.0863 0.3452 …поверх черновика gemini-3.1-flash-lite
+E4 vs E0 16 −1.25 [−3.19, +0.75] 0.2663 0.5326 …поверх черновика glm-4.7
+E5 vs E0 16 −1.69 [−3.00, −0.25] 0.0444 0.2222 …поверх черновика grok-4.3
+E0 vs D0 16 +3.25 [+2.25, +4.19] 0.0001 0.0007 ✔ КОНТРОЛЬ: редактор поверх якоря
+```
+
+**Читается так.** Единственный контраст, переживший поправку на множественность, — КОНТРОЛЬ:
+боевой редактор поверх якорного черновика покупает **+3.25** ошибки. Заголовок эксп-21
+(+2.50 · +2.81 на контаминированной книге) **воспроизвёлся на чистом материале и усилился**.
+
+**Ни один альтернативный черновик не улучшил финал.** Все пять перевесов ОТРИЦАТЕЛЬНЫ (финал
+поверх якоря лучше), ни один не проходит Холма, и все, кроме `E2`, лежат ниже порога различимости
+2.03. `E2` (−2.56) порог по величине превышает, но `p` Холма 0.3452 — как установленный факт не
+держится.
+⚠ **И даже эта величина хрупка (см. §15):** в собственном арме `E2` на единице `474f822806` стоит
+клетка с `finish=length`; на подвыборке без затронутых обрывом единиц `E2` даёт −1.83 при пороге
+2.33, то есть порог не превышает. Вывод секции от этого не меняется — он и был «не держится».
+
+⚠ **Знак важнее величины.** Прогноз пре-рега был «разброс панели черновиков будет БОЛЬШЕ разброса
+редакторов»; замер его не подтвердил в ожидаемой форме: разброс есть, но он весь в сторону
+УХУДШЕНИЯ относительно самой дешёвой модели, а не в пользу дорогих.
+
+### 4.2 Ось переноса en (6 единиц)
+
+```
+E1 vs E0 +1.83 · E2 vs E0 +1.17 · E3 vs E0 +2.17 · E4 vs E0 +0.17 · E5 vs E0 +1.83
+E0 vs D0 +0.00 [−2.50, +2.33] ← КОНТРОЛЬ
+```
+**Знак противоположен zh, и контроль равен НУЛЮ.** На английском исходнике боевой редактор поверх
+якорного черновика не покупает ничего, а финалы поверх других черновиков скорее лучше. Ни один
+контраст не проходит поправку (n=6). Это тот же класс, что нашёл эксп-21 («на паре, где исходник
+уже гипотактичен, разница между топологиями сжимается»), но здесь он доведён до нуля на контроле.
+⇒ **Вывод про жильца черновой роли на пару en НЕ переносится.**
+
+### 4.3 Детерминированные оси (шума не имеют, судьи не требуют)
+
+**(в) Эхо-мина — свойство МАТЕРИАЛА, а не константа модели:**
+```
+deepseek-v4-flash zh 1/16 = 6% en 0/6
+gemini-3.1-flash-lite zh 2/16 = 12% en 0/6 (одна единица не покупается вовсе: 3/3 попытки — эхо)
+luna · deepseek-v4-pro · glm-4.7 · grok-4.3 zh 0/16 en 0/6
+```
+Приор эксп-21 на 蛊真人 — **31–38%** у той же модели с той же ручкой. На этой книге **6%**.
+Гипотеза 5.3 эксп-21 («частота эха — измеримое свойство материала») подтверждена вторым замером.
+
+**(г) Батарея и покрытие канона (знаменатель 1124 упоминания):**
+```
+арм ханьцзы типогр потеря вел. покрытие канона
+D[deepseek-v4-flash] 12 27 18 0.909
+E[deepseek-v4-flash] 0 18 16 0.882
+D[gpt-5.6-luna] 75 23 16 0.970
+E[gpt-5.6-luna] 2 14 17 0.932
+D[deepseek-v4-pro] 5 20 14 0.913
+E[deepseek-v4-pro] 2 12 16 0.860
+D[gemini-3.1-flash-lite] 6 24 18 0.927
+E[gemini-3.1-flash-lite] 0 11 18 0.877
+D[glm-4.7] 53 21 19 0.960
+E[glm-4.7] 0 12 17 0.903
+D[grok-4.3] 2 22 16 0.946
+E[grok-4.3] 3 16 17 0.893
+```
+Два независимых от судьи факта: **редактор вычищает утечку письменности** (75 и 53 ханьцзы у
+черновиков luna и glm-4.7 → 2 и 0 после редактора) и **редактор теряет канон У ВСЕХ ШЕСТИ
+жильцов** (−0.027 … −0.057), несмотря на закон-блок. Второе — линия эксп-21 §16.1, здесь она
+получает шесть независимых подтверждений вместо одного.
+
+**(д) Цена связки и проекция на книгу (1500 единиц):**
+```
+жилец черновик редактор связка книга $
+gemini-3.1-flash-lite 0.00407 0.00466 0.00873 13.09
+deepseek-v4-flash 0.00130 0.00769 0.00899 13.49
+gpt-5.6-luna 0.00368 0.00557 0.00926 13.89
+deepseek-v4-pro 0.00415 0.00751 0.01167 17.50
+glm-4.7 0.00664 0.00559 0.01223 18.34
+grok-4.3 0.00993 0.00525 0.01518 22.77
+```
+⚠ Редактор поверх ЯКОРНОГО черновика дороже, чем поверх любого другого ($0.00769 против
+$0.00466–0.00559): якорь пишет длиннее, и это удорожает второй проход. Итоговая цена связки у
+якоря всё равно вторая снизу.
+
+## §5 ДОВЕСОК `D_/A` НА КУПЛЕННОМ СЫРЬЕ ЭКСП-21 (генерации не покупались)
+
+```
+D_ против A 16 единиц −1.56 [−2.88, −0.25] p=0.0526 декой 16/16 ✔
+```
+Эксп-21 на пуле двух проходов: −2.00, p=0.0157, Холма 0.0630 — не прошёл. **Третья независимая
+судейская популяция воспроизвела ЗНАК и не дала значимости.** Статус контраста «однопроходка без
+мандата против связки» остаётся **НЕ УСТАНОВЛЕНО** — теперь на трёх замерах, а не на одном.
+
+---
+
+## §6 Ф3 — ПАНЕЛЬ РЕДАКТОРОВ ПОВЕРХ ОДНОЙ ЯКОРНОЙ БАЗЫ
+
+### 6.0 Контроли (читать ДО чисел)
+```
+ДЕКОЙ −4.75 · поймано 16/16 ✔
+ПОЛ −0.25 [−1.88, +1.56] n=16 · sd 3.53 · ст.ошибка 0.88 ⇒ порог различимости 2.47 · ЧЕСТЕН
+БЛИЗНЕЦ побайтно одинаковых пар 1 · перевес ровно ноль 1/1 ✔
+```
+⚠ Близнец — не дефект рига, а **находка**: `deepseek-v4-flash` в роли редактора на единице
+`2484f16eac` вернул черновик **побайтно без единой правки** (проверено сверкой текстов: 1 клетка
+из 62 в Ф3). Класс «no-op редактора», который эксп-12 ловил на grok reasoning-off, воспроизвёлся
+у дешёвой модели без всякого гашения размышления.
+
+### 6.1 Судейство (16 единиц, семейство в ключе ДО ответов, Холм по 4)
+```
+контраст ед. перевес 95% ДИ p p Холма
+R0 vs F 16 +4.44 [+3.00, +6.12] 0.0001 0.0002 ✔ КОНТРОЛЬ: редактор поверх базы
+R2 vs R0 16 −2.12 [−3.81, −0.75] 0.0094 0.0188 ✔ glm-5 против боевого
+R3 vs R0 13 −2.54 [−4.00, −1.00] 0.0146 0.0188 ✔ deepseek-v4-flash против боевого
+R4 vs R0 16 −4.75 [−6.12, −3.44] 0.0000 0.0001 ✔ grok-4.3 против боевого
+```
+**Все четыре контраста проходят поправку, все три альтернативных редактора значимо ХУЖЕ боевого**
+`deepseek-v4-pro` над побайтно одной базой, и все перевесы выше порога различимости 2.47.
+
+**Это первый замер проекта, в котором роль редактора вообще разделилась.** Эксп-21 на паре
+{dspro, glm-5} давал `A_law/B` = −0.69 с нулём внутри интервала и писал «движки неразличимы»;
+здесь на чистом материале `glm-5` проигрывает −2.12 значимо. Причина расхождения не установлена:
+кандидаты — другая книга, другая судейская популяция, другой банк.
+
+**R1 `gemini-3.1-pro-preview` — обязательный кандидат промта — в таблицу не попал.** Он взят в
+фазу, как требует промт, и выдал не число, а **отказ на проводе**: 1 клетка из 16 (см. §6.3).
+
+### 6.2 Детерминированные оси Ф3
+```
+арм ед. ханьцзы типогр канон покрытие $/ед
+R0 deepseek-v4-pro 16 0 18 991/1124 0.882 0.00769
+R1 gemini-3.1-pro-preview 1 0 0 95/97 0.979 0.14702
+R2 glm-5 16 5 27 1049/1124 0.933 0.01249
+R3 deepseek-v4-flash 13 0 21 830/911 0.911 0.00177
+R4 grok-4.3 16 2 18 1009/1124 0.898 0.01426
+БАЗА (черновик) 16 12 27 1022/1124 0.909 0.00130
+```
+⚠ **Детерминированный контур и судья дают РАЗНЫЙ порядок.** Победитель по судье (`deepseek-v4-pro`)
+имеет ХУДШЕЕ покрытие канона из всей панели — 0.882 против 0.933 у `glm-5` и 0.909 у самого
+черновика. То есть боевой редактор покупает язык и верность, а платит терминологией. Это прямое
+подтверждение строки 152 бэклога («расхождение детерминированного контура и судьи») и довод к
+тому, что покрытие канона обязано быть ОТДЕЛЬНЫМ гейтом, а не следствием выбора редактора.
+
+### 6.3 ⛔ ОБЯЗАТЕЛЬНЫЙ КАНДИДАТ ПРОМТА НЕ ОТРАБОТАЛ НА ПРОВОДЕ
+
+`gemini-3.1-pro-preview` взят в Ф3 безусловно, как требует промт. Что он выдал:
+
+* **скрин Ф1:** четыре отказа HTTP 503 «This model is currently experiencing high demand», $0,
+ страница статуса Google инцидентов не показывала. Состав отказов — **две редакторские клетки и
+ две переводческие**; переводческие взялись с ретрая, редакторские не взялись ни разу, поэтому
+ редакторской цены у модели нет вовсе, а скрин-вердикт ПРОВАЛ вынесен по переводческой цене;
+* **Ф3:** **1 клетка из 16**. Один вызов держал замок **74.5 минуты** без ошибки и без ответа
+ (таймаут клиента 900 с умножался на внутренние ретраи SDK). После жёсткой границы (300 с,
+ `max_retries=0`) и объявленного подмножества в 8 единиц новых клеток не прибавилось;
+* **цена состоявшейся клетки:** $0.14702 — вход 4485, видимый выход 2532, **тарифицировано 11 504**
+ выходных токена, то есть в 4.5 раза больше видимого. Сама скрытая часть = 11 504 − 2 532 =
+ **8 972 токена, втрое больше ответа**; видно это ТОЛЬКО в `total_tokens`.
+
+⇒ **Гипотеза эксп-04 «gemini-pro — аутлаер прозы» этим паком НЕ проверена и остаётся открытой.**
+Проверить её на текущем слаге нельзя не из-за качества, а из-за операбельности: модель в
+редакторской роли на боевой длине входа не отдаёт результат. Это ответ на заказ промта, но ответ
+про ПРОВОД, а не про прозу.
+
+---
+
+## §12 АННУЛИРОВАННЫЙ ПРОХОД `p2en` — ЧТО СЛУЧИЛОСЬ И ЧЕГО ЭТО СТОИЛО
+
+Первый выпуск английского прохода ушёл к судьям **без контроля**: декой не был посажен ни в одну
+единицу, и заметил это не гейт, а вскрытие голосов. Причина — книго-специфичность посадок: правила
+порчи были написаны под лексику 蛊真人 (имена, термины культивации), и на новой книге ни одно из
+них не срабатывало, а функция посадки при нулевом совпадении молча возвращала текст без изменений.
+
+Проход **аннулирован целиком** (голоса лежат в `annulled-p2en-run1/`, в счёт не идут), посадки
+переписаны книго-независимыми — грамматика и типографика вместо лексики: снятие отрицания, подмена
+величины, выдуманное слово, слом диалоговой вёрстки, слипание абзацев. После переписывания декой
+ловится **6/6** на en и 16/16 на zh.
+
+**Цена ошибки — 6 агент-запусков из 60** (списаны безвозвратно) и задержка фазы. **Урок в прибор:**
+контроль, который может «не сработать» молча, не контроль. Посадка теперь падает, если не изменила
+текст, — молчаливый ноль больше невозможен.
+
+⚠ **Девиация ключей, объявляю.** Ключи слепых меток проходов `p2zh`/`p2en` были **пере-созданы в
+22:16:22**, то есть ПОСЛЕ того, как судьи начали отвечать (`p2zh` с 21:24). Пере-создание внесло
+одно поле — `decoy_from`, базу, из которой посажен декой; задания судьям (21:08–21:10) не менялись
+ни одним байтом, перестановка меток не менялась тоже. Пред-объявление семейства доказывается не
+этими файлами, а фриз-коммитами `43381fe` (19:54) и `af6d885` (20:38), которые старше и заданий, и
+ответов. Но артефакт, на который ссылается печать, моложе ответов — и это надо знать тому, кто
+будет проверять провенанс.
+
+---
+
+## §12а ВСКРЫТИЕ ЕДИНИЦ ПЕРЕД СРАВНИТЕЛЬНЫМ ВЫВОДОМ (D39.61, мандат самопроверки)
+
+Запрет выводить на агрегате, не открыв единиц, — жёсткий. Вот что видно глазами на единице
+`d6cbc0179d` (2114 знаков исходника), по одной стороне каждого решающего сравнения.
+
+**Ф3, `R0` боевой `deepseek-v4-pro` против `R2` пограничного `glm-5`.** Оба дают связную русскую
+прозу; провала нет ни у одного, и это согласуется с перевесом 2.12 при пороге прохода 2.47.
+Различия стилистические: боевой идиоматичнее и охотнее сворачивает придаточные («нипочём не стал
+бы», «прибрал к рукам»), `glm-5` ближе к буквальному и дробит абзацы сильнее. **Вывод вскрытия:
+назвать `glm-5` проигравшим по этой паре текстов нельзя — только пограничным**, что и сделано в §2.
+
+**Ф2, черновик `glm-4.7` (`D4`, −5.44) против якорного.** Здесь разрыв виден сразу и он не
+стилистический: черновик `glm-4.7` идёт **по одному предложению на строку** — абзацной структуры
+нет вовсе. Это отказ по оси ФОРМА, а не по языку, и он объясняет и величину −5.44, и то, почему
+боевой редактор вытягивает этот черновик до −1.25: редактор переливает текст в абзацы. **Именно
+это вскрытие даёт механизм за находкой §13** — редактор работает компрессором различий черновика,
+потому что худшие различия черновиков оказались вёрсточными, а не смысловыми.
+
+⚠ Обратная сторона: если бы панель черновиков состояла из моделей, ломающих СМЫСЛ, а не вёрстку,
+компрессия могла бы и не сработать. Вывод «черновик решает меньше редактора» верен для той полосы
+качества, которую дала эта панель, и не обязан держаться шире.
+
+---
+
+## §13 СВЕРКА ПРОГНОЗОВ §0.5 С ФАКТОМ (заказана промтом: расхождение = находка)
+
+| # | прогноз (объявлен ДО покупок) | факт | итог |
+|---|---|---|---|
+| 1 | скрин убьёт 2–4 из 11; вероятны `kimi-k2.6`, `grok-4.3`, `mistral-large-latest`, `gemini-3.1-flash-lite` | убил **4**: kimi, mistral, `gemini-3.6-flash`, `gemini-3.1-pro-preview` | **число верно, состав — наполовину нет** |
+| 2 | разброс панели ЧЕРНОВИКОВ будет БОЛЬШЕ разброса панели редакторов | финалы поверх черновиков: размах **2.12**; редакторы: размах **2.63** | **ОПРОВЕРГНУТ, знак обратный** |
+| 3 | `gemini-3.1-pro` не окажется аутлаером; перевес ляжет ниже пола | 1 клетка из 16 — перевеса нет | **НЕ РАЗРЕШЁН** (провод, не проза) |
+| 4 | победитель черновой роли решится ценой; перевесы лягут в 1–3 ошибки при поле ≈1.9 | перевесы 0.44–2.56, пол **2.03**, правило ничьей применено | **ПОДТВЕРЖДЁН** |
+| 5 | эхо-мина `deepseek-v4-flash` на этой книге 25–40% | **6%** (1/16) | **ПРОМАХ в 4–6 раз** |
+
+**Прогноз 2 — самая содержательная находка сверки, и она против посылки заказа.** Промт строил
+дизайн на том, что черновик двигает ~70% качества финала, поэтому и панель черновиков заказана
+шире. Данные говорят иначе: КАК ТЕКСТ черновики расходятся огромно (размах **7.25**: от +1.81 до
+−5.44), но после боевого редактора этот разброс сжимается до 2.12, тогда как выбор самого
+редактора даёт 2.63. **Редактор работает компрессором различий черновика.** Отсюда практический
+вывод, которого пак не планировал: деньги на верхний тир имеет смысл тратить в редакторской роли,
+а не в черновой, — и именно это независимо подтверждают обе рекомендации (дешёвый черновик,
+дорогой редактор).
+
+**Прогноз 5 промахнут вчетверо, и это не «подтверждение гипотезы 5.3», а промах.** Направление
+гипотезы («эхо — свойство материала») он поддерживает, но как ожидание величины прогноз был
+неверен, и подавать его подтверждением значило бы задним числом переписывать, что предсказывалось.
+
+---
+
+## §13а МЕХАНИЧЕСКАЯ СВЕРКА С БУКВОЙ ПРОМТА (сделана ПОСЛЕ приёмки, по требованию владельца)
+
+⚠ **Зачем отдельно от §14.** Критик полноты в §14 писала та же голова, что вела пак, и он
+унаследовал её слепоту: проверял по МОДЕЛИ промта, а не по промту. Здесь промт пройден
+построчно, включая придаточные. Все найденные пробелы закрыты $0; два — не закрываемы без
+покупок и объявлены девиациями.
+
+| требование промта | где стоит в промте | статус |
+|---|---|---|
+| Sol-пакет **(б)** — слепые пары финалистов «по готовности Ф2/Ф3» | хвост п.4 Ф0 | ⛔ **НЕ ЗАКРЫТО и не будет** (поправка фазы Д): пакет сгенерирован, но владельцу в этом виде НЕ отдаётся — та же конструкция `N_UNITS_B = 3`, что развалила пакет (а) (§16). Прежняя пометка «выпущен» — маркер дефекта «артефакт вместо действия». Замена: контур на заданиях абсолютного рига |
+| **Вахта dspro** — сверка вендор-changelog маппинга эффорта в Ф0 и ПЕРЕД Ф3 | буллет ЗАКОНа про DeepSeek | **было не сделано → сделано**, девиация по времени (§14) |
+| Гейт **ToS Z.AI** пометить **в ИТОГе** | последняя строка промта | было в §14 → **вынесено в ИТОГ (§9а)** |
+| цена связки p50/**p95** по единицам **против боевой пары** | строка «Отчёт» | квантили добавлены, **сравнение с боевой парой сделано явным** (§5) |
+| вывод на агрегате до **вскрытия единиц** запрещён (D39.61) | мандат самопроверки | **не было документировано → §12а**, дало механизм за находкой §13 |
+| ось **(а)** — абсолютный суд черновика как ТЕКСТА | п. (а) Ф2 | было куплено и не напечатано → **§1** |
+| позиционный наклон «замерь и **вычти**» | буллет ЗАКОНа про судейство | было не вычтено → **§7, все четыре прохода** |
+| en-единицы Kristoff, `129b73ad5a` выброшен | п.2 Ф0 | исполнено: 6 единиц, выброшена |
+| закон-блок `HEADER_LAW_PLAIN` во всех армах + банкнота на переводческом | банк-закон D39.104 | ⛔ **на zh исполнено, на en НЕТ** (поправка фазы Д, п.2): 72 en-клетки куплены без блока, потому что банк среза собран только по zh — §9 п.9. Селфтест `prompts.py` сторожит zh-ветку; en-ветки у него нет |
+| клетка dspro-над-flash покупается **один раз**, в Ф3 переиспользуется | конец Ф2 | исполнено (`editors.py::text_of`) |
+| спорных кандидатов СТИЛЬ-осей скрином не решать | урок эксп-20 в Ф1 | исполнено: скрин убивал по цене, формату и пустому выходу, не по качеству прозы |
+| Claude-модели в армы не брать | буллет ЗАКОНа | исполнено: моделей семьи судьи в ростере нет |
+| **дешёвый + сильный тир КАЖДОГО провайдера**, срез только при >16 | абзац «Кандидаты» Ф1 | ⛔ **НЕ ИСПОЛНЕНО — девиация, см. ниже** |
+| вторая база Ф3 · строка 153 | опции Ф3 | не гнались — решение сессии, §9 п.12 |
+
+### ⛔ Девиация: панель кандидатов срезана сильнее разрешённого
+
+Промт разрешал до **16** кандидатов и требовал у каждого провайдера **оба тира** — дешёвый
+(кандидаты черновой роли) и сильный (кандидаты редакторской); «провайдера целиком не выкидывать —
+хотя бы один слот» — это ПОЛ правила на случай среза, а не цель. Я срезал до **11**, и срез снёс
+сильный тир у четырёх провайдеров из семи: у OpenAI, Moonshot, xAI и Mistral остался один слот
+(`roster.CUT`: `gpt-5.6-terra`, `kimi-k3`, `grok-4.5`, `mistral-medium-latest`, три слага Z.AI).
+Обоснование по каждой срезанной модели в коде есть — требование «с обоснованием» выполнено, —
+но запас на пять кандидатов не был использован.
+
+**Что это меняет для читателя.** Панель Ф3 состояла из того, что прошло суженный скрин; сильные
+тиры четырёх провайдеров в замере не участвовали вовсе. Поэтому вывод «`deepseek-v4-pro` побеждает
+значимо» верен ВНУТРИ панели и **не является утверждением о лучшем доступном редакторе**: не
+проверены `gpt-5.6-terra`, `kimi-k3`, `grok-4.5`. Починка требует покупок и в этот пак не входит;
+для следующего — это первая строка работы.
+
+---
+
+## §15 КОРОТКИЕ КЛЕТКИ В СЫРЬЕ ЭТОГО ПАКА — ⚠ ИНТЕРПРЕТАЦИЯ ПЕРЕПИСАНА ФАЗОЙ Д
+
+⚠ **Эта секция дважды говорила неверное, и оба раза — не про числа, а про ПРИЧИНУ.** Первая
+редакция (в отчёте пака 23) сравнивала длину клетки с медианой ПУЛА и объявила оборванными три
+клетки, которые нормальны: они из английского пула, где расширения нет. Вторая (здесь) правило
+починила — отношение к СВОЕМУ исходнику, — но объявила, что «пять клеток из шести завершились
+штатно и ни один гейт этого не ловит». **Это опровергнуто приёмкой №16 и пере-снято фазой Д: пять
+клеток из шести — ЭХО ИСХОДНИКА, и эхо-гейт поймал каждую.**
+
+**Замер (доля письменности в выходе, $0, `content` из сырья):**
+
+```
+клетка исх вых ханьцзы кирилл finish что это
+tp2-draft-D-deepseek-v4-flash-474f822806 2194 2279 1.000 0.000 stop ЭХО → ре-ген
+ └ …-r2 2194 7814 0.001 0.999 stop ×3.6, СУДИЛСЯ как D0
+tp2-floor-p1-D-deepseek-v4-flash-474f822806 2194 2281 1.000 0.000 stop ЭХО → ре-ген
+ └ …-r2 2194 7994 0.001 0.997 stop ×3.6
+tp2-floor-p2-D-deepseek-v4-flash-41599f30df 2116 2167 1.000 0.000 stop ЭХО → ре-ген
+ └ …-r2 2116 6950 0.001 0.999 stop ×3.3
+tp2-floor-p2-D-deepseek-v4-flash-ef9cd38ec4 2155 2190 1.000 0.000 stop ЭХО → ре-ген
+ └ …-r2 2155 2211 1.000 0.000 stop ЭХО ещё раз
+ └ …-r3 2155 7672 0.000 0.998 stop ×3.6
+tp2-draft-D-gemini-3.1-flash-lite-ed068182cf 2517 2448 1.000 0.000 stop ЭХО 3/3 — арм не куплен
+tp2-edit-E-deepseek-v4-pro-474f822806 2194 1638 0.000 1.000 length ОБРЫВ, и он СУДИЛСЯ
+```
+
+Что именно судилось — доказано подписями `sig` в ключах слепых меток (обратное сопоставление
+подписи с файлом на диске): **эхо-попытки в ключах не встречаются ни разу** — это главное и это
+доказано. ⚠ Уточнение фазы Д: буквально «судился ре-ген» верно для ОДНОЙ клетки — черновика
+`474f822806-r2` (арм `D0` в `p2zh`, `F` в `p3`). У трёх флор-клеток в ключах подписаны не сами
+ре-гены, а РЕДАКТУРЫ поверх них; что редактура снята именно с ре-гена, подписью `sig` не
+доказывается — в записи редакторской клетки хеша входа нет. Утверждение «эхо не судилось» от
+этого не страдает, но провенанс «поверх чего сделана редактура» — открытая дыра прибора, и её
+надо закрыть полем `input_sig` в записи клетки. Арм `D3`/`E3` на единице `ed068182cf` отсутствует вовсе — это и есть объявленная в §7
+«единица, которая не покупается» (отсюда `E3 n=15`).
+
+⇒ **Первые пять — не пропуск гейта, а его РАБОТА, видимая в леджере: эхо поймано, ре-ген оплачен,
+судился здоровый текст.** Настоящий пропуск ровно один — обрыв `finish=length`.
+
+### ⛔ А вот РАЗМЕР КЛАССА ОБРЫВОВ больше, чем думали обе прежние редакции
+
+Пере-счёт по ВСЕМ ключам пака (404 судимых клетки опознаны на диске по подписи): **`finish=length`
+у семи судимых слотов, **четыре** различных текста (три из них судились дважды — в `p2zh` и в
+`p3`, потому что клетка `R0` куплена один раз и переиспользована).**
+
+```
+проход арм клетка len $
+p2zh CTRLfloorB tp2-floor-p2-E-deepseek-v4-flash-bbb54d0043 5319 0.015336
+p2zh CTRLfloorB tp2-floor-p2-E-deepseek-v4-flash-f6da9f76b2 7581 0.015200
+p2zh E0 tp2-edit-E-deepseek-v4-flash-41599f30df 5759 0.015727
+p2zh E2 tp2-edit-E-deepseek-v4-pro-474f822806 1638 0.015241
+p3 CTRLfloorB tp2-floor-p2-E-deepseek-v4-flash-bbb54d0043 5319 0.015336
+p3 CTRLfloorB tp2-floor-p2-E-deepseek-v4-flash-f6da9f76b2 7581 0.015200
+p3 R0 tp2-edit-E-deepseek-v4-flash-41599f30df 5759 0.015727
+```
+
+⚠ **И это не весь класс обрывов в паке — только его СУДИМАЯ часть.** Отдельно на диске лежат
+**шесть клеток `finish=length` с ПУСТЫМ `content` на $0.216770**: три скрина `kimi-k2.6` и три
+редакторские клетки `deepseek-v4-flash` фазы Ф3. В ключи слепых меток они не попали (судить нечего),
+поэтому в таблицу выше не входят — но фраза «пере-счёт по ВСЕМ ключам» описывает именно судимую
+часть, и объявить границу надо здесь. Пустые клетки Ф3 — тот же режим, из-за которого `R3` считан
+по 13 единицам из 16 (§7), а пустые клетки скрина — основание дисквалификации `kimi-k2.6`.
+
+Читать судимую часть надо не как «ещё четыре клетки», а как **две конкретные беды**:
+* **две клетки стоят в ШУМОВОМ ПОЛУ обеих фаз.** Пол — калибровка прибора, из него берётся порог
+ различимости; недоработавший вызов в паре завышает разброс, а значит и порог;
+* **одна клетка стоит в БАЗЕ ВСЕХ КОНТРАСТОВ.** `tp2-edit-E-deepseek-v4-flash-41599f30df` — это
+ `E0` в Ф2 и он же `R0` в Ф3 (клетка куплена один раз и переиспользована, §0-Ф3). То есть на
+ единице `41599f30df` каждый контраст обеих фаз сравнивался ПРОТИВ оборванного текста.
+
+### Чувствительность выводов — замерена, а не оценена
+
+Leave-out по единицам, несущим хоть одну оборванную судимую клетку (пол пере-считан по оставшимся):
+
+⚠ **Первая редакция этой таблицы печатала ТОЛЬКО пороги — вторичный критерий, — а решающее
+правило пака есть `p` Холма < 0.05 (§0-Ф2). Поймано ревью; пере-считано точным знаковым тестом.**
+
+```
+Ф2 (p2zh) все 16 единиц без 4 затронутых (n=12)
+пол/порог +0.19 / 2.03 +0.17 / 2.33
+E1 vs E0 −0.44 p 0.6711 Холм 0.6711 −1.25 p 0.1855 Холм 0.2739
+E2 vs E0 −2.56 p 0.1058 Холм 0.3452 −1.83 p 0.1084 Холм 0.2739 ← был выше порога
+E3 vs E0 −1.87 p 0.0863 Холм 0.3452 −2.18 p 0.0098 Холм 0.0391 ✔ ← стал ЗНАЧИМ
+E4 vs E0 −1.25 p 0.2663 Холм 0.5326 −2.08 p 0.0913 Холм 0.2739
+E5 vs E0 −1.69 p 0.0444 Холм 0.2222 −2.25 p 0.0078 Холм 0.0391 ✔ ← стал ЗНАЧИМ
+E0 vs D0 +3.25 p 0.0001 Холм 0.0007 ✔ +3.33 p 0.0020 Холм 0.0117 ✔ КОНТРОЛЬ
+
+Ф3 (p3) все 16 единиц без 3 затронутых (n=13)
+пол/порог −0.25 / 2.47 −0.31 / 2.41
+R2 vs R0 −2.12 p 0.0094 Холм 0.0188 ✔ −1.92 p 0.0103 Холм 0.0205 ✔
+R3 vs R0 −2.54 p 0.0146 Холм 0.0188 ✔ −2.60 p 0.0430 Холм 0.0430 ✔
+R4 vs R0 −4.75 p 0.0000 Холм 0.0001 ✔ −4.85 p 0.0002 Холм 0.0010 ✔
+R0 vs F +4.44 p 0.0001 Холм 0.0002 ✔ +4.38 p 0.0005 Холм 0.0015 ✔ КОНТРОЛЬ
+```
+
+**Что устояло и что нет.** Решающая таблица пака — Ф3, выбор редактора — не двигается ни в одном
+вердикте: все четыре контраста проходят Холма и до, и после. Заголовочные выводы («ни один
+альтернативный черновик не улучшил финал», «все три альтернативных редактора хуже боевого»,
+«контроль воспроизводится в обеих фазах») держатся.
+
+⛔ **Не устояли ДВА напечатанных утверждения §1, а не одно, как объявляла первая редакция этой
+секции.** (1) «Все, кроме `E2`, лежат ниже порога различимости»: без затронутых единиц `E2` тоже
+ниже порога. (2) **«Ни один не проходит Холма»**: на подвыборке `E3` и `E5` проходят (Холм 0.0391).
+Второе нашла не сессия — ревью, и нашла ровно потому, что сессия сэкономила усилие на дорогой
+статистике: напечатала пороги и не пере-считала `p`.
+
+**Куда это ведёт содержательно.** Обе правки направлены ПРОТИВ альтернативных черновиков: убрав
+единицы, где сам якорный финал `E0` оборван, мы делаем `E0` сильнее, и два альтернативных черновика
+становятся значимо ХУЖЕ. То есть заголовок «ни один альтернативный черновик не улучшил финал» после
+поправки не ослабевает, а усиливается — но формулировка «ни один не проходит Холма» верна только
+для полного набора, и так её и надо читать.
+
+⚠ Это **leave-out, а не чистый пере-замер**: выбрасывание единиц меняет и n, и пол. Оно отвечает на
+вопрос «зависит ли вывод от затронутых единиц» и не отвечает на вопрос «какими были бы числа, если
+бы клетки не оборвались». Второго ответа не существует без покупки.
+
+**В прибор — гейт СУЖЕН до класса `finish=length`** (так предписала приёмка, и замер это
+подтверждает). Отношение «длина клетки к длине исходника» как признак не нужно: эхо-класс уже
+покрыт эхо-гейтом, а на разноязычных пулах отношение даёт ложные срабатывания. Нужное правило
+одно и детерминированное: **клетка с `finish=length` в судейскую пачку не допускается — ни боевым
+армом, ни половиной шумового пола.** Провод сообщает этот флаг сам, деньги за клетку уже уплачены,
+и стоит гейт $0. В фазе Д он стоит ДО судейства и жёсткий; для этого пака клетки уже куплены и
+отсужены, поэтому здесь класс объявлен с замером чувствительности, а не пере-куплен.
+
+## §14 КРИТИК ПОЛНОТЫ ПО ФАЗАМ (заказан промтом)
+
+Что заказано · что закрыто · что осталось. Пишется по фазам, а не по впечатлению от объёма.
+
+**Ф0 — материал.** Заказано: свежий незаражённый zh-срез, проба контаминации, банк, Sol-пакет (а).
+Закрыто всё. Не закрыто: **ToS-роутинг помечен, но не проверен исполнением** — `sexually-explicit`
+объявлен FORBIDDEN для `deepseek` и `zai` по договорам провайдеров (маршрутизация правом, не
+поведением), и гейт прав снял по этому основанию две главы; но самого отказа моделей на
+чувствительном материале пак не наблюдал, потому что материал этого класса убрал (§11).
+
+**Ф1 — скрин.** Заказано: профиль каждого кандидата, пороги до замера, сверка прогнозов. Закрыто:
+таблица по всем 11, три починки исполнением, четвёртая (отступ на 5xx) — в §2.3. Сверка прогнозов
+доделана здесь, §13. **Кредиты xAI:** промт фиксировал их исчерпанными 07.08 с пометкой «вопрос у
+владельца»; `grok-4.3` скринился и покупался во всех фазах, то есть пополнение состоялось — строка
+об этом промтом требовалась и печатается здесь (§9 п.6 — подтвердить владельцу).
+
+**Ф2 — панель черновиков.** Заказано пять осей: (а) черновик как текст, (в) эхо, (г) батарея и
+канон, (д) цена. Закрыто всё, **но ось (а) была отсуждена и не напечатана** — исправлено (§1),
+и это самый крупный пропуск пака: замер существовал в сырье и отсутствовал в отчёте.
+
+**Ф3 — панель редакторов.** Заказано: обязательный кандидат, резерв эксп-21, дешёвый и дорогой
+над одной базой. Закрыто. Не закрыто: **вторая база Ф3** (опция промта) не гналась — решение
+сессии, не владельца (§9 п.12); **`R1` не дал панели** (1 клетка); **`R3` считан по 13/16** из-за
+оплаченного пустого выхода (§7).
+
+**Вахта dspro (ЗАКОН промта: сверка вендор-changelog маппинга эффорта в Ф0 и ПЕРЕД Ф3).**
+Отработана по вендор-доке ($0, без вызовов): `reasoning_effort` в API DeepSeek присутствует,
+управление размышлением — через `thinking`; последняя запись вендор-changelog, затрагивающая
+`deepseek-v4-flash`, датирована **31.07.2026** и описывает пере-обучение при той же архитектуре и
+размере — ровно тот факт, что уже зафиксирован D39.61 («слаг живой ≠ модель та же»). **Смены
+маппинга эффорта после фриза Ф2 нет**, поэтому стоп и пинг, которые промт предписывает при смене,
+не требовались. ⚠ Сверка сделана ПОСЛЕ покупок, а не в Ф0 и перед Ф3, как предписано; я это
+объявляю девиацией, а не выдаю за исполненное вовремя. Задним числом она безопасна ровно потому,
+что изменения не нашлось: найдись оно — пришлось бы аннулировать фазу, а не дописывать абзац.
+
+**Сквозное.** Закрыто: позиционный наклон замерен по всем четырём проходам и вычтен (§7),
+p50/p95 по единицам напечатаны (§5), деньги сведены двумя путями. Не закрыто и переносится:
+**строка 153** (уравненный мандат `D`/`D_`) не гналась; **Sol вернулся 09.08, но арбитраж не
+состоялся** (§16: пакет (а) прогнан владельцем и развалился по конструкции — `N_UNITS = 3`, дрейф
+шкалы ×2.6, зеркальные пары в разных сессиях; пакет (б) в этом виде не выпускать), поэтому
+судейского контура вне одной модельной семьи у ЭТОГО пака по-прежнему нет (§11) — ⚠ поправка фазы
+Д, п.3: прежняя формулировка «Sol не вернулся» неверна; **фактическая величина двойной оплаты Ф2**
+невосстановима в принципе (§8).
+
+**Чего в паке нет и не могло быть.** Вебновелл-срез взят одной книгой и одной парой: всё, что
+зависит от свойств текста, обязано пере-меряться на следующей. Ни один вывод пака не является
+утверждением о моделях вообще — только о них на этой паре, этой книге и этих 16 единицах.
diff --git a/eval/README.md b/eval/README.md
index a4bc94cc..a4b3ad19 100644
--- a/eval/README.md
+++ b/eval/README.md
@@ -28,6 +28,24 @@
| `providers.json` | базовые URL/слаги для зондов | квирки — `docs/experiments/00-provider-quirks.md`, читать ПЕРЕД любым вызовом |
| `promptlang/` | Р6+P4 (закрыт D39.61): язык промптов · анти-эхо wiring · верность митигаций; действующая редакция выводов = §12 отчёта `docs/archive/reports/POLYGON_PROMPTLANG_ANTIECHO_2026-07-31.md` | `slugcheck.py`, `wire_probe.py` (сырой провод), `run_arms.py`, `spend.py` (два пути счёта денег — прогонять оба), `judge_*.py` |
+### Действующие харнессы паков 18–23 и фазы Д (добавлено эксп-22 08.08 — в карте их не было)
+| Папка | Эксперимент | Что внутри и чем полезно дальше |
+|---|---|---|
+| `bank_arbitration/` | exp-16/18 + research/24 | `editor_wire_probe.py` — рендер боевых промптов (`render`, `strip_comments`, `editor_block`, `HEADER_LAW_PLAIN`/`HEADER_LAW_CLAUSE`), окна и посадки; `inject_probe.py` — глоссарий-блок переводчика и `render_translator`. **Это фундамент всех последующих паков**, ломать нельзя |
+| `editor_contract/` | exp-19 (дифф-контракт Q4b) | `probe.py`, `apply.py` (аппликатор, самотест 15/15), `effort_probe.py` (вахта маппинга эффорта DeepSeek) |
+| `editor_harness/` | exp-20 (контракт редактора × модели) | `probe.py` — сборка сообщений по контрактам (`full`/`diff-*`/`locate`/`direct`), en-зеркала промптов |
+| `role_topology/` | **exp-21 (топология ролей)** | `absjudge.py` — абсолютный судейский риг (слепые метки · декой · пол · обоснование цитатой · семейство в ключе · Холм), `battery.py` — 11 детерминированных проверок, `buy.py` — касса с проекционным гардом, `bakeoff.py`/`pair_en.py` — армы zh/en, `itog.py`/`verify_report.py` — образец гейтов чисел, `ruff.toml` — линтер зоны |
+| `tenant_panel/` | **exp-22 (панель жильцов)** | `material.py` — добыча и отбор невиданного среза, `contam.py` — проба контаминации, `bank.py` — банк книги + метрика покрытия канона, `roster.py` — ростер и цены с провенансом, `money.py` — касса с фриз-гейтом (покупка отказывает незакоммиченному коду) и `configure()` для пере-настройки под другой пак, `judge.py` — тот же `absjudge`, настроенный на другую панель, `sol.py` — пакеты внешнему судье (⚠ конструкция признана негодной, §16 отчёта) |
+| `editor_tier/` | **exp-23 (сильный тир редактора и граница `glm-5`)** | `screen.py` — скрин сильного тира, `panel.py` — панель редактур поверх якорной базы, `judge.py` — два прохода (`tier`/`border`) + режим `--run <прогон>` для ОТДЕЛЬНЫХ прогонов того же прохода (репликация), `money.py` — касса эксп-22 через `configure`, `itog23.py`/`verify23.py` — пере-счёт и гейт чисел (сторожат пол, порог, декой, наклон и СВЕЖЕСТЬ разбора), `solscore.py` — счёт ответов ВНЕШНЕГО судьи вне семьи Claude |
+| `dovodka/` | **фаза Д (доводка 22/23, заказ владельца 10.08)** | `runs.py` — журнал агент-сессий: запись ДО запуска суб-агента, кап 80, замок против ГОНКИ сессий по одному токену (в паке 23 гонка стоила соответствия голосов и сырья на 5 единицах) |
+
+> ⚠ **Две мины импорта, на которые наступает каждый новый пак.**
+> (1) Модуль `probe.py` существует И в `editor_contract/`, И в `editor_harness/` — какой из них
+> подхватится, решает ПОРЯДОК `sys.path.insert` в вызывающем файле. Проверять фактический путь
+> (`probe.__file__`), а не догадываться.
+> (2) Сырьё паков живёт в `~/books/<пак>/`; путь зашит константой. Если каталог переехал —
+> харнесс молча видит пустоту и печатает «голосов нет» вместо ошибки.
+
### 18+ / корпус-билдеры
| Скрипт | Эксперимент | Заметки |
|---|---|---|
diff --git a/eval/conformance.py b/eval/conformance.py
index 0e863335..b231f7c9 100644
--- a/eval/conformance.py
+++ b/eval/conformance.py
@@ -25,9 +25,22 @@
проходящая улика, а не что улика доказывает именно это требование: связь «требование ↔ улика»
устанавливает автор таблицы, и подменить её ничего не мешает. Это сдвигает работу с «вспомнить всё
из прозы» на «объявить улику заранее» — но не отменяет чтения.
+
+⚠⚠ **ДВА ДЕФЕКТА, НАЙДЕННЫЕ ПРИЁМКОЙ НА ПАКЕ 23, ЧИНЯТСЯ ЗДЕСЬ** (фаза Д, 10.08):
+
+1. **Реестр жил в эфемерном scratchpad.** `--final` печатал зелёное против таблицы, которой в
+ репозитории нет: читатель пере-снять её не мог, а автор мог править её после результата. Теперь
+ в режиме `--final` источник ОБЯЗАН быть отслежен git и совпадать с закоммиченным (тот же
+ фриз-гейт, что у кассы `tenant_panel/money.py`). Обход — `TM_ALLOW_UNFROZEN=1`, громко.
+2. **Отчёт проверял сам себя.** Большая часть улик была грепом формулировок ТОГО ЖЕ документа, то
+ есть «в тексте написано, что сделано» выдавалось за «сделано». Механически отличить «улика на
+ печать» от «улики на дело» нельзя, поэтому инструмент их СЧИТАЕТ и печатает долю: улика,
+ упоминающая сам файл реестра или файл отчёта, помечается `сам-себя` и выносится в итог
+ отдельной строкой. Ноль таких улик не требуется — требуется, чтобы читатель видел их число.
"""
from __future__ import annotations
+import os
import subprocess
import sys
from pathlib import Path
@@ -36,6 +49,22 @@ REPO = Path("/home/ubuntu/projects/textmachine")
MARK = ""
+def frozen(src: Path) -> tuple[bool, str]:
+ """Реестр отслежен git и совпадает с закоммиченным? Это и есть его фриз."""
+ try:
+ rel = str(src.resolve().relative_to(REPO))
+ except ValueError:
+ return False, f"реестр вне репозитория: {src}"
+ r = subprocess.run(["git", "-C", str(REPO), "ls-files", "--error-unmatch", rel], # noqa: S603,S607
+ capture_output=True, text=True)
+ if r.returncode:
+ return False, f"реестр НЕ в git: {rel} — гейт проверял бы файл, которого нет у читателя"
+ d = subprocess.run(["git", "-C", str(REPO), "diff", "--quiet", "HEAD", "--", rel]) # noqa: S603,S607
+ if d.returncode:
+ return False, f"реестр отличается от закоммиченного: {rel}"
+ return True, ""
+
+
def rows(md: str) -> list[tuple[str, str, str]]:
"""Строки таблицы требований: (ID, требование, улика)."""
if MARK not in md:
@@ -97,19 +126,46 @@ def main() -> int:
if not src:
print("не указан файл")
return 1
- md = (REPO / src if not src.startswith("/") else Path(src)).read_text(encoding="utf-8")
- bad = pend = 0
+ path = REPO / src if not src.startswith("/") else Path(src)
+ md = path.read_text(encoding="utf-8")
+ bad = pend = selfref = 0
print(f"СВЕРКА С БУКВОЙ ЗАКАЗА: {src} [{mode[2:]}]\n")
+
+ # ⚠ ФРИЗ РЕЕСТРА. Гейт против незакоммиченной таблицы ничего не доказывает: её можно дописать
+ # ПОД результат, и читатель этого не увидит. Пак 23 прошёл `--final` против реестра, лежавшего
+ # в сессионном scratchpad, — и «соответствие заказу проверено» из этого не следовало.
+ if mode == "--final":
+ ok, why = frozen(path)
+ if not ok:
+ if os.environ.get("TM_ALLOW_UNFROZEN") == "1":
+ print(f"⚠ ФРИЗ-ГЕЙТ РЕЕСТРА ОТКЛЮЧЁН (TM_ALLOW_UNFROZEN=1): {why}\n")
+ else:
+ print(f"⛔ {why}\n зафризь реестр коммитом ДО сверки (промт фазы Д, нормы рига)")
+ return 1
+
+ # Улика, которая грепает сам реестр/отчёт, доказывает «напечатано», а не «сделано».
+ # ⚠ Первая редакция искала имя как ОТДЕЛЬНОЕ слово с запретом «/» слева — то есть любая
+ # улика с ПУТЁМ (а иначе улику не пишут) была невидима, и строка «сам-себя: 0» печаталась
+ # всегда. Поймано ревью исполнением. Ищем подстроку имени, плюс имена ОТЧЁТОВ фазы: улика,
+ # грепающая отчёт, доказывает «напечатано», а не «сделано».
+ selfnames = {path.name, *(Path(a).name for a in args if a.endswith(".md"))}
+ selfnames |= {f.name for f in (REPO / "docs" / "experiments").glob("2[23]-*.md")}
for rid, req, ev in rows(md):
st, detail = check(ev)
if st == "ОЖИДАЕТ" and mode == "--final":
st = "ПРОВАЛ"
detail = "улики нет, а пак сдаётся"
+ mark = ""
+ if any(n in ev for n in selfnames):
+ selfref += 1
+ mark = " [сам-себя]"
bad += st == "ПРОВАЛ"
pend += st == "ОЖИДАЕТ"
- print(f"[{st:8s}] {rid:6s} {req[:78]}" + (f" ← {detail}" if detail else ""))
+ print(f"[{st:8s}] {rid:6s}{mark} {req[:70]}" + (f" ← {detail}" if detail else ""))
total = len(rows(md))
print(f"\nтребований {total} · ожидают {pend} · провалов {bad}")
+ print(f"улик вида «греп своего же документа» ({'сам-себя'}): {selfref} из {total}"
+ + (" ⚠ они доказывают, что НАПЕЧАТАНО, а не что СДЕЛАНО" if selfref else ""))
if bad:
print("СВЕРКА НЕ ПРОЙДЕНА")
return 1 if bad else 0
diff --git a/eval/editor_tier/itog23.py b/eval/editor_tier/itog23.py
new file mode 100644
index 00000000..5df4f8c0
--- /dev/null
+++ b/eval/editor_tier/itog23.py
@@ -0,0 +1,199 @@
+#!/usr/bin/env python3
+"""ПЕРЕ-СЧЁТ ВСЕХ НЕСУЩИХ ЧИСЕЛ ПАКА ИЗ СЫРЬЯ. $0.
+
+Число, которое нельзя пере-снять командой из репозитория, ничем не отличается от числа по памяти.
+Здесь каждый блок ИТОГа печатается заново из персистированных артефактов, деньги — двумя
+независимыми путями (касса и пере-счёт из сырых `usage` × прайс).
+
+Запуск: eval/.venv/bin/python eval/editor_tier/itog23.py
+"""
+from __future__ import annotations
+
+import json
+import statistics as st
+import sys
+from pathlib import Path
+
+REPO = Path("/home/ubuntu/projects/textmachine")
+ZONE = Path(__file__).resolve().parent
+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 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
+
+
+JU = _load("judge_et", ZONE / "judge.py")
+PAN, SCR, MONEY, MAT = JU.PAN, JU.PAN.SCR, JU.MONEY, JU.MAT
+ROSTER = SCR.ROSTER
+OUT = MONEY.OUT
+
+
+def h(t: str) -> None:
+ print(f"\n═══ {t} ═══")
+
+
+def material() -> None:
+ h("МАТЕРИАЛ")
+ old, new = JU.P22.M.units_zh(), MAT.units_new()
+ print(f" единиц эксп-22 (приколоты манифестом) {len(old)} · глав "
+ f"{sorted(u['chapter'] for u in old)}")
+ ln = sorted(len(u["source"]) for u in new)
+ print(f" НОВЫХ единиц {len(new)} · глав {sorted(u['chapter'] for u in new)}")
+ print(f" знаков {ln[0]}…{ln[-1]} медиана {ln[len(ln) // 2]} · "
+ f"макс. близость {JU.P22.M._pairwise_max_sim(new):.3f}") # noqa: SLF001
+ print(f" снято гейтом прав из своего пула: {MAT.rights_dropped(set()) or '—'}"
+ if False else f" пересечений с эксп-22: "
+ f"{len({u['uid'] for u in new} & {u['uid'] for u in old})}")
+
+
+def screen() -> None:
+ h("ФАЗА A — СКРИН СИЛЬНОГО ТИРА (пороги эксп-22, не свои)")
+ rs = SCR.result()
+ print(f" {'модель':16s}{'вердикт':>10s}{'ед.':>4s}{'$/ед edit':>11s}{'размышл':>9s} причины")
+ for m, r in rs.items():
+ print(f" {m:16s}{r['verdict']:>10s}{r['n']:4d}{(r['price_editor'] or 0):11.5f}"
+ f"{r['reasoning_share']:9.1%} {'; '.join(r['why'])}")
+ print(f" прошли: {[m for m, r in rs.items() if r['verdict'] == 'прошёл']}")
+ print(" СПРАВКА (полный критерий эксп-22, с переводческим порогом, которого нет в пре-реге):")
+ for m, r in rs.items():
+ print(f" {m:16s}{r['verdict_full22']:>10s} $/ед transl "
+ f"{(r['price_translator'] or 0):.5f}")
+
+
+def judged(pass_: str) -> None:
+ h(f"СУДЕЙСТВО, проход {pass_}")
+ JU.setup(pass_)
+ import absjudge as AJ # noqa: PLC0415
+ if not AJ._by_unit(): # noqa: SLF001
+ print(" голосов нет")
+ return
+ AJ.controls()
+ print()
+ AJ.score()
+
+
+def positional() -> None:
+ h("ПОЗИЦИОННЫЙ НАКЛОН — ЗАМЕРЕН И ВЫЧТЕН (требование ЗАКОНа)")
+ for pass_ in JU.PASSES:
+ JU.setup(pass_)
+ import absjudge as AJ # noqa: PLC0415
+ if not AJ._by_unit(): # noqa: SLF001
+ continue
+ print(f" --- {pass_} ---")
+ AJ.position()
+ print()
+
+
+def money_two_ways() -> None:
+ h("ДЕНЬГИ — ДВА НЕЗАВИСИМЫХ ПУТИ")
+ led = {ph: MONEY.Guarded(ph).spent() for ph in MONEY.TAGS}
+
+ def indep(rec: dict) -> float:
+ m = rec.get("model")
+ if m not in ROSTER.CANDIDATES:
+ return rec.get("cost_usd", 0.0)
+ _, _, pin, pc, pout, _t, _s = ROSTER.CANDIDATES[m]
+ pt, cd = rec.get("prompt_tokens", 0), rec.get("cached_tokens", 0)
+ out = ROSTER.billed_output(m, rec.get("completion_tokens", 0),
+ rec.get("reasoning_tokens", 0),
+ rec.get("total_tokens", 0), pt)
+ return (pt - cd) / 1e6 * pin + cd / 1e6 * pc + out / 1e6 * pout
+
+ tot2 = {ph: 0.0 for ph in led}
+ empty = []
+ for ph, globs in MONEY.TAGS.items():
+ for g in globs:
+ for f in OUT.glob(g):
+ r = json.loads(f.read_text(encoding="utf-8"))
+ tot2[ph] += indep(r)
+ if r.get("cost_usd", 0) > 0 and not (r.get("content") or "").strip():
+ empty.append((f.name, r["cost_usd"]))
+ print(f" {'фаза':6s}{'касса':>12s}{'пере-счёт':>12s}{'расхождение':>13s}{'потолок':>10s}")
+ for ph in MONEY.TAGS:
+ mark = " ⛔ ПРОБОЙ" if led[ph] > MONEY.CEILINGS[ph] + 1e-9 else ""
+ print(f" {ph:6s}{led[ph]:12.6f}{tot2[ph]:12.6f}{led[ph] - tot2[ph]:13.6f}"
+ f"{MONEY.CEILINGS[ph]:10.2f}{mark}")
+ print(f" {'ПАК':6s}{sum(led.values()):12.6f}{sum(tot2.values()):12.6f}"
+ f"{sum(led.values()) - sum(tot2.values()):13.6f}{MONEY.PACK_CEILING:10.2f}")
+ if empty:
+ t = sum(c for _n, c in empty)
+ print(f" оплачено за ПУСТОЙ выход: {len(empty)} вызовов ${t:.5f} "
+ f"({t / max(1e-9, sum(led.values())) * 100:.1f}% пака)")
+ for n, c in sorted(empty, key=lambda x: -x[1]):
+ print(f" {n} ${c:.5f}")
+
+
+def _cost(f: Path) -> float:
+ return json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0) if f.exists() else 0.0
+
+
+def economics() -> None:
+ """⚠ Строка R0 считается ИЗ СЫРЬЯ эксп-22, а не через `hasattr(P22,"edit_cost")`.
+
+ Прежняя редакция спрашивала у эксп-22 метод, которого у него нет (есть только `draft_cost`), —
+ строка молча выходила нулевой и отфильтровывалась, а в отчёт попадали числа, снятые где-то ещё
+ и друг с другом не сходившиеся (0.00545 × 1500 = $8.18, напечатано $13.45). Поймано приёмкой.
+ Колонки печатаются ДВУМЯ семействами: редактура отдельно и СВЯЗКА черновик+редактор — сравнивать
+ связку одного арма с редактурой другого нельзя, а именно это отчёт и делал.
+ """
+ h("ЦЕНА РЕДАКТОРСКОЙ КЛЕТКИ, p50 И p95 ПО ЕДИНИЦАМ")
+ us = JU.P22.M.units_zh()
+ T22 = JU.P22.OUT
+ base = [_cost(T22 / f"{JU.P22.tag_draft(PAN.ANCHOR, u['uid'])}.json") for u in us]
+ rows = [("R0 " + PAN.BATTLE,
+ [_cost(T22 / f"{JU.P22.tag_edit(PAN.ANCHOR, u['uid'])}.json") for u in us])]
+ for i, m in enumerate(PAN.survivors(), start=1):
+ rows.append((f"T{i} {m}", [_cost(OUT / f"{PAN.tag_c(m, u['uid'])}.json") for u in us]))
+
+ def q(cs: list[float], p: int) -> float:
+ s = sorted(c for c in cs if c > 0)
+ return s[min(len(s) - 1, -(-p * len(s) // 100) - 1)] if s else 0.0
+
+ print(f" {'арм':26s}{'ед.':>5s}{'p50 edit':>10s}{'p95 edit':>10s}"
+ f"{'p50 связка':>12s}{'книга p50':>12s}{'книга p95':>12s}")
+ for name, cs in rows:
+ nz = [c for c in cs if c > 0]
+ if not nz:
+ continue
+ link = [c + b for c, b in zip(cs, base, strict=True) if c > 0 and b > 0]
+ print(f" {name:26s}{len(nz):5d}{st.median(nz):10.5f}{q(nz, 95):10.5f}"
+ f"{(st.median(link) if link else 0):12.5f}"
+ f"{(st.median(link) if link else 0) * 1500:12.2f}{q(link, 95) * 1500:12.2f}")
+ print(f" якорный черновик (общая база связки): p50 {st.median([b for b in base if b > 0]):.5f}"
+ if any(base) else " якорного черновика в сырье нет")
+
+
+def agent_runs() -> None:
+ h("АГЕНТ-ЗАПУСКИ")
+ for p in JU.PASSES:
+ v = OUT / f"aj-{p}-votes"
+ t = OUT / f"aj-{p}-tasks"
+ print(f" {p:8s} заданий {len(list(t.glob('*.txt'))) if t.exists() else 0} · "
+ f"голосов принято {len(list(v.glob('*.json'))) if v.exists() else 0}")
+
+
+def main() -> None:
+ print("ИТОГ ПАКА «СИЛЬНЫЙ ТИР» — ВСЕ НЕСУЩИЕ ЧИСЛА ИЗ СЫРЬЯ ($0)")
+ material()
+ screen()
+ economics()
+ for p in JU.PASSES:
+ try:
+ judged(p)
+ except Exception as e: # noqa: BLE001
+ print(f"\n═══ СУДЕЙСТВО {p} ═══\n не считается: {type(e).__name__}: {e}")
+ positional()
+ money_two_ways()
+ agent_runs()
+ print("\nВСЁ ВЫШЕ ПЕРЕ-СНЯТО ИЗ СЫРЬЯ.")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/eval/editor_tier/judge.py b/eval/editor_tier/judge.py
index d4279269..f0384021 100644
--- a/eval/editor_tier/judge.py
+++ b/eval/editor_tier/judge.py
@@ -15,6 +15,7 @@
"""
from __future__ import annotations
+import hashlib
import json
import sys
from pathlib import Path
@@ -47,7 +48,8 @@ PASSES = ("tier", "border")
def arm_map(pass_: str) -> dict[str, str]:
if pass_ == "tier":
- return {"R0": PAN.BATTLE, **{f"T{i}": m for i, m in enumerate(PAN.survivors(), start=1)}}
+ return {"R0": PAN.BATTLE, **{f"T{i}": m for i, m in enumerate(PAN.survivors(), start=1)},
+ "F": PAN.ANCHOR} # голый якорный черновик — база позитивного контроля
return {"R0": PAN.BATTLE, "R2": PAN.BORDER}
@@ -58,8 +60,13 @@ def arms(pass_: str) -> tuple[str, ...]:
def family(pass_: str) -> list[tuple[str, str, str]]:
"""Семейство объявляется ДО выпуска и впечатывается в ключ."""
if pass_ == "tier":
- return [(a, "R0", f"редактор сильного тира {a} против боевого над ТОЙ ЖЕ базой")
- for a in arms(pass_) if a != "R0"]
+ fam = [(a, "R0", f"редактор сильного тира {a} против боевого над ТОЙ ЖЕ базой")
+ for a in arms(pass_) if a not in ("R0", "F")]
+ # ⚠ ПОЗИТИВНЫЙ КОНТРОЛЬ, обещанный планом и пропущенный первой редакцией: покупает ли
+ # боевой редактор что-нибудь поверх голого черновика. Без него проход не отличает
+ # «армы равны» от «судья ничего не различает».
+ fam.append(("R0", "F", "КОНТРОЛЬ: покупает ли боевой редактор поверх якорного черновика"))
+ return fam
return [("R2", "R0", "пограничный glm-5 против боевого, n удвоено до 32")]
@@ -78,8 +85,14 @@ def text_of(pass_: str):
# единица новая → тексты фазы B; единица эксп-22 → его же клетки
if u["uid"] in new_uids:
return PAN.text_b(arm, u["uid"])
- return P22.edit_text(PAN.BATTLE if arm == "R0" else PAN.BORDER, u["uid"]) \
- if arm == "R0" else _ed22(u["uid"])
+ # ⚠ R0 — боевой редактор поверх ЯКОРНОГО черновика. В именовании эксп-22 арм
+ # редактуры назван по ЧЕРНОВИКУ-жильцу, а не по редактору: `edit_text("dspro")` дало
+ # бы редактуру поверх ЧЕРНОВИКА dspro, то есть ДРУГУЮ базу, и контраст сравнивал бы
+ # армы над разными основаниями. Поймано приёмкой. Направление смещения НЕ замерено —
+ # прежний комментарий утверждал «против R2» без замера, утверждение снято.
+ return P22.edit_text(PAN.ANCHOR, u["uid"]) if arm == "R0" else _ed22(u["uid"])
+ if arm == "F":
+ return PAN.P22.draft_text(PAN.ANCHOR, u["uid"])
return PAN.text_c(arm, u["uid"])
return hook
@@ -103,17 +116,67 @@ def floor_of(pass_: str):
def decoy_base(slots: dict) -> str:
- for a in ("R0", "T1", "R2"):
- if a in slots and (slots[a] or "").strip():
- return a
- return next(iter(slots))
+ """⚠ Возвращать ТЕКСТ, а не имя арма. Первая редакция возвращала имя — риг сажал порчу в
+ строку «R0», получал 0 замен при пороге 2 и МОЛЧА не создавал декой: ноль контролей в обоих
+ проходах. Это ровно тот отказ, которым эксп-22 потерял проход en, и он воспроизвёлся здесь.
+
+ ⚠ ДОНОР ПОРЧИ УРАВНОВЕШЕН ПО АРМАМ. Вторая редакция брала R0 всегда (замер: 16/16 и 32/32), а
+ R0 стоит вторым армом в КАЖДОМ контрасте обоих проходов: если присутствие испорченного близнеца
+ вообще двигает оценку донора, оно двигает её в одну сторону во всех контрастах сразу, и в
+ `border` — где контраст один — опровергнуть это нечем. Донор раздаётся РОВНО: единицы
+ прохода нумеруются по хешу, донор берётся по остатку номера — судье не предсказуем, по проходу
+ поделён поровну (чистый хеш давал 22/10 на 32 единицах, перекос остался бы). Принятый проход
+ `tier` отсужен ДО этой правки, его декой во всех 16 единицах взят из R0 — объявленное
+ ограничение, см. отчёт §12.
+ """
+ battle = [a for a in ("R0", "T1", "T2", "T3", "R2") if (slots.get(a) or "").strip()]
+ if not battle:
+ return next(t for t in slots.values() if (t or "").strip())
+ rank = _DONOR_RANK.get(hashlib.sha1(slots[battle[0]].encode("utf-8")).hexdigest(), 0) # noqa: S324,E501
+ return slots[battle[rank % len(battle)]]
-def setup(pass_: str) -> None:
+_DONOR_RANK: dict[str, int] = {}
+
+
+def _rank_units(pass_: str) -> None:
+ """Номер единицы для ровной раздачи донора. Ключ — хеш текста ПЕРВОГО боевого арма: он же
+ считается в `decoy_base`, куда номер единицы не передаётся. Порядок — по хешу, а не по главе:
+ соседние главы не должны систематически получать одного донора."""
+ _DONOR_RANK.clear()
+ hook = text_of(pass_)
+ keyed = []
+ for u in units(pass_):
+ for a in ("R0", "T1", "T2", "T3", "R2"):
+ t = hook(a, u) or ""
+ if t.strip():
+ keyed.append(hashlib.sha1(t.encode("utf-8")).hexdigest()) # noqa: S324
+ break
+ for i, k in enumerate(sorted(keyed)):
+ _DONOR_RANK[k] = i
+
+
+def setup(pass_: str, run: str = "") -> None:
+ """`run` — имя ОТДЕЛЬНОГО прогона того же прохода (репликация). Пустое = боевой прогон.
+
+ ⚠ Заведено поправкой фазы Д (чек-лист приёмки №16, п.9). Репликацию гоняли, копируя каталоги
+ руками: имя каталога выводилось только из `PASS`, поэтому разбор шёл под боевым именем и
+ проставил голосам репликации имена судей БОЕВОГО жребия — то есть ровно то, что репликация
+ и меняет. Здесь у прогона свои каталоги и своя карта судей, а ключ/соль/метки остаются
+ общими (иначе это была бы не репликация, а другой замер).
+ """
+ _rank_units(pass_)
AJ.configure(OUT=OUT, KEYS=KEYS, PASS=pass_, ARMS=arms(pass_), ARMS_2B=(),
FAMILY=family(pass_), TEXT_HOOK=text_of(pass_),
UNITS_HOOK=lambda: units(pass_), FLOOR_HOOK=floor_of(pass_),
- DECOY_HOOK=decoy_base, SRC_LANG="китайском")
+ DECOY_HOOK=decoy_base, SRC_LANG="китайском",
+ DIRS_HOOK=None, JUDGES_HOOK=None)
+ if run:
+ jmap = json.loads((OUT / f"{run}-JUDGES.json").read_text(encoding="utf-8"))
+ AJ.configure(
+ DIRS_HOOK=lambda: (OUT / f"aj-{pass_}-tasks", OUT / f"{run}-answers",
+ OUT / f"{run}-votes"),
+ JUDGES_HOOK=lambda tok, _mtime: jmap.get(tok, "?"))
KEYS.mkdir(parents=True, exist_ok=True)
@@ -161,9 +224,24 @@ if __name__ == "__main__":
elif a[0] == "--emit":
setup(a[1])
AJ.emit()
+ elif a[0] == "--run":
+ # Разбор/счёт ОТДЕЛЬНОГО прогона: --run <прогон> <проход> [--ingest|--score|--controls]
+ setup(a[2], run=a[1])
+ what = a[3] if len(a) > 3 else "--score"
+ if what == "--ingest":
+ AJ.ingest()
+ elif what == "--controls":
+ AJ.controls()
+ else:
+ AJ.score()
elif a[0] == "--ingest":
setup(a[1])
AJ.ingest()
+ # ⚠ Раздача судей персистируется ЗДЕСЬ. `log_run` был написан и ни разу не вызван: во всех
+ # голосах пака стоит `judge='?'`, согласие судей посчитать нечем, а «агент-запусков 38» —
+ # счёт по памяти. Токены сессии передаются списком: --ingest <проход> <ток> <ток> …
+ if len(a) > 2:
+ print(f"запуск #{log_run(a[1], a[2:], note='судейская сессия')} записан")
elif a[0] == "--score":
setup(a[1])
AJ.controls()
diff --git a/eval/editor_tier/panel.py b/eval/editor_tier/panel.py
index 6207f1a0..8ab2c4c2 100644
--- a/eval/editor_tier/panel.py
+++ b/eval/editor_tier/panel.py
@@ -42,6 +42,7 @@ PR = _load("prompts22", TP / "prompts.py")
P22 = _load("panel22", TP / "panel.py")
MAT = _load("material_et", ZONE / "material.py")
SCR = _load("screen_et", ZONE / "screen.py")
+import bakeoff as BO # noqa: E402
MONEY = SCR.MONEY
ROSTER = SCR.ROSTER
@@ -90,6 +91,13 @@ def cmd_b_run() -> None:
if not d.strip():
print(f" {u['uid']}: черновика нет — редактуры пропущены")
continue
+ # ⚠ ГЕЙТ БРАКА ЧЕРНОВИКА БЫЛ НАПИСАН И НЕ ПОДКЛЮЧЁН. Фаза проверяла только «строка пуста»,
+ # поэтому эхо-мина прошла на боевую базу: `63ab55ac5c` — 1798 ханьцзы и НОЛЬ кириллицы при
+ # `finish=stop`. Отчёт из-за этого объявлял класс «не ловится ни одним гейтом» — неверно:
+ # `draft_reject_reason` его ловит, звать было некому. Поймано аудитом закрытия заказа.
+ if (why := BO.draft_reject_reason(d)):
+ print(f" {u['uid']}: черновик забракован ({why}) — редактуры НЕ покупаются")
+ continue
law = PR.law_block(PR.terms_in(u["source"]))
for m in (BATTLE, BORDER):
te = tag_b("edit", m, u["uid"])
@@ -180,7 +188,15 @@ def cmd_floor_run() -> None:
def floor_pair(uid: str):
- """(генерация 1, генерация 2) самого дешёвого арма панели."""
+ """(генерация 1, генерация 2) самого дешёвого арма панели.
+
+ ⚠ ДЕФЕКТ, ОБЪЯВЛЕННЫЙ И НЕ ПОЧИНЕННЫЙ В ЭТОМ ПАКЕ: первая половина пары — это САМА боевая
+ клетка `T1`, а не отдельная генерация. Следствие ровно одно, и оно не в числах пола (пол
+ считается по РАЗНОСТИ двух генераций и остаётся честным), а в контроле «БЛИЗНЕЦ»: он ловит
+ побайтные дубли, и здесь единственным дублем оказывается пара `CTRLfloorA`≡`T1` в 16/16 —
+ то есть контроль сравнивает `T1` с самим собой и ничего не проверяет. Чинить надо ПОКУПКОЙ
+ отдельной первой генерации, поэтому в этом паке дефект только объявлен (отчёт §12).
+ """
import json # noqa: PLC0415
a = text_c("T1", uid)
f = MONEY.OUT / f"{tag_floor(uid, 2)}.json"
diff --git a/eval/editor_tier/solscore.py b/eval/editor_tier/solscore.py
new file mode 100644
index 00000000..d4b55aee
--- /dev/null
+++ b/eval/editor_tier/solscore.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python3
+"""ПЕРЕ-СЧЁТ ГОЛОСОВ ВНЕШНЕГО СУДЬИ (не-Claude) ПО ПРОХОДУ border. $0.
+
+Судья другой семьи отсуживал ТЕ ЖЕ файлы заданий, что и агенты-судьи пака (`aj-border-tasks/`), и
+клал ответы в `sol-border/`. Поэтому его счёт сопоставим с моим текст-в-текст, а не «другой замер».
+
+⚠ Разбор здесь СВОЙ, а не `absjudge.ingest`: тот пишет в боевой каталог голосов и затёр бы прогон
+пака. Правила приёмки ответа те же — каждая ненулевая ось названа и подпёрта цитатой.
+
+Запуск: eval/.venv/bin/python eval/editor_tier/solscore.py
+"""
+from __future__ import annotations
+
+import json
+import math
+import random
+import re
+import statistics as st
+import sys
+from pathlib import Path
+
+OUT = Path.home() / "books" / "editor-tier"
+# Проход задаётся аргументом: `border` (исполнен 09.08) и `tier` (заказан фазой Д, п.Д2).
+PASS = "border"
+KEY = OUT / "blind-keys" / f"{PASS}-KEY.json"
+ANSW = OUT / f"sol-{PASS}"
+AXES = ("ВЕРНОСТЬ", "ТЕРМИН", "ЯЗЫК", "ФОРМА")
+SEED = 20260809
+
+
+def votes() -> tuple[dict, list]:
+ key = json.loads(KEY.read_text(encoding="utf-8"))
+ ok, bad = {}, []
+ for f in sorted(ANSW.glob("*.txt")):
+ k = key.get(f.stem)
+ if not k:
+ bad.append((f.stem, "токена нет в ключе"))
+ continue
+ txt, unit, fail = f.read_text(encoding="utf-8"), {}, None
+ for lab, meta in k.items():
+ ax = {}
+ for a in AXES:
+ m = re.search(rf"^{lab}-{a}[ \t]*:[ \t]*(\d+)[ \t]*$", txt, re.M)
+ if m:
+ ax[a] = int(m.group(1))
+ if len(ax) != len(AXES):
+ fail = f"не разобран блок {lab}"
+ break
+ wm = re.search(rf"^{lab}-ПОЧЕМУ[ \t]*:[ \t]*(.*?)(?=^\S+-(?:{'|'.join(AXES)}|ПОЧЕМУ)"
+ rf"[ \t]*:|\Z)", txt, re.M | re.S)
+ why = (wm.group(1) if wm else "").strip()
+ miss = [a for a in AXES if ax[a] and not re.search(rf"(? float:
+ """Точный при малом n, иначе сид-Монте-Карло: воспроизводимость важнее последней цифры."""
+ rnd, s, hits, trials = random.Random(SEED), sum(m), 0, 200_000 # noqa: S311
+ for _ in range(trials):
+ if abs(sum(x * rnd.choice((1, -1)) for x in m)) >= abs(s) - 1e-12:
+ hits += 1
+ return (hits + 1) / (trials + 1)
+
+
+def main() -> int:
+ global PASS, KEY, ANSW # noqa: PLW0603
+ args = [a for a in sys.argv[1:] if not a.startswith("-")]
+ if args:
+ PASS = args[0]
+ KEY, ANSW = OUT / "blind-keys" / f"{PASS}-KEY.json", OUT / f"sol-{PASS}"
+ print(f"проход {PASS} · ключ {KEY.name} · ответы {ANSW}")
+ if not ANSW.exists() or not list(ANSW.glob("*.txt")):
+ print(f"ответов внешнего судьи нет в {ANSW}")
+ return 1
+ ok, bad = votes()
+ print(f"принято единиц {len(ok)} · отвергнуто {len(bad)}")
+ for t, r in bad:
+ print(f" ⛔ {t}: {r}")
+ dec = [u[u["CTRLdecoy"]["decoy_from"]]["errors"] - u["CTRLdecoy"]["errors"] for u in ok.values()
+ if "CTRLdecoy" in u and u["CTRLdecoy"].get("decoy_from") in u]
+ flo = [u["CTRLfloorA"]["errors"] - u["CTRLfloorB"]["errors"] for u in ok.values()
+ if "CTRLfloorA" in u and "CTRLfloorB" in u]
+ # Семейство прохода: у `border` один контраст, у `tier` — три плюс позитивный контроль.
+ fam = ([("R2", "R0")] if PASS == "border"
+ else [("T1", "R0"), ("T2", "R0"), ("T3", "R0"), ("R0", "F")])
+ pairs = [(a, b, [u[b]["errors"] - u[a]["errors"] for u in ok.values() if a in u and b in u])
+ for a, b in fam]
+ pairs = [(a, b, m) for a, b, m in pairs if len(m) >= 3]
+ if not (dec and flo and pairs):
+ print("контролей или боевых армов не хватает")
+ return 1
+ caught = sum(1 for x in dec if x < 0)
+ thr = 2.8 * st.pstdev(flo) / math.sqrt(len(flo))
+ print(f"\n ДЕКОЙ {st.mean(dec):+.2f} · поймано {caught}/{len(dec)}"
+ + ("" if caught == len(dec) else " ⛔ сессия внешнего судьи аннулируется"))
+ print(f" ПОЛ {st.mean(flo):+.2f} · n={len(flo)} sd={st.pstdev(flo):.2f} → ПОРОГ {thr:.2f}")
+ for a, b, m in pairs:
+ print(f" {a} vs {b} n={len(m)} {st.mean(m):+.2f} p={sign_p(m):.4f} "
+ f"{'РАЗЛИЧИМ' if abs(st.mean(m)) > thr else 'ниже порога различимости'}")
+ battle = {x for a, b, _ in pairs for x in (a, b)}
+ lv = [r["errors"] for u in ok.values() for a, r in u.items() if a in battle]
+ print(f" уровень (медиана боевых клеток) {st.median(lv):.1f}")
+ return 0 if caught == len(dec) else 1
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/eval/editor_tier/verify23.py b/eval/editor_tier/verify23.py
new file mode 100644
index 00000000..f9070d93
--- /dev/null
+++ b/eval/editor_tier/verify23.py
@@ -0,0 +1,189 @@
+#!/usr/bin/env python3
+"""ВЕРИФИКАТОР ЧИСЕЛ ОТЧЁТА ПАКА. $0. Ненулевой код возврата = отчёт не сдаётся.
+
+`itog23.py` пере-считывает числа из сырья; этот файл сверяет их с ТЕКСТОМ отчёта. Разделение
+нужно потому, что «число посчиталось» и «в отчёте напечатано именно оно» — разные утверждения.
+
+⚠ ЧЕГО ЭТОТ ГЕЙТ НЕ ДЕЛАЕТ. Он сторожит, что число ПРИСУТСТВУЕТ в тексте, а не что предложение
+вокруг числа истинно. Приёмка эксп-22 нашла ровно этот класс: «все перевесы выше порога 2.47» при
+всех сходящихся числах. Пороговые и сравнительные утверждения проверяются только чтением; за
+соответствие ЗАКАЗУ отвечает отдельный инструмент `eval/conformance.py`.
+"""
+from __future__ import annotations
+
+import json
+import re
+import statistics as sst
+import sys
+from pathlib import Path
+
+REPO = Path("/home/ubuntu/projects/textmachine")
+ZONE = Path(__file__).resolve().parent
+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 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
+
+
+IT = _load("itog23", ZONE / "itog23.py")
+JU, PAN, SCR, MONEY, MAT = IT.JU, IT.PAN, IT.SCR, IT.MONEY, IT.MAT
+
+REPORT = REPO / "docs" / "experiments" / "23-editor-tier.md"
+BAD = 0
+
+
+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 _norm(x: str) -> str:
+ """Пробелы не значимы; типографский минус U+2212 и ASCII-дефис — один знак."""
+ return re.sub(r"\s+", "", x).replace("−", "-").replace("–", "-")
+
+
+def has(text: str, s: str) -> bool:
+ return _norm(s) in _norm(text)
+
+
+def main() -> int:
+ if not REPORT.exists():
+ print("отчёта нет")
+ return 1
+ t = REPORT.read_text(encoding="utf-8")
+
+ print("=== МАТЕРИАЛ ===")
+ new = MAT.units_new()
+ ck(f"новых единиц {len(new)}", has(t, f"{len(new)} нов") or has(t, f"{len(new)} НОВ"))
+ ck("главы новых единиц напечатаны",
+ all(has(t, str(c)) for c in sorted({u["chapter"] for u in new})[:3]))
+
+ print("\n=== ФАЗА A ===")
+ for m, r in SCR.result().items():
+ ck(f"{m}: вердикт {r['verdict']} назван", has(t, m))
+ if r["price_editor"]:
+ ck(f"{m}: цена редактора {r['price_editor']:.5f}", has(t, f"{r['price_editor']:.5f}"))
+ ck("оба вердикта скрина различены (пре-рег и полный критерий эксп-22)",
+ has(t, "пре-рег") and has(t, "переводческ"))
+
+ print("\n=== ЦЕНА ===")
+ us = JU.P22.M.units_zh()
+ T22 = JU.P22.OUT
+
+ def cost(f: Path) -> float:
+ return json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0) if f.exists() else 0.0
+
+ base = [cost(T22 / f"{JU.P22.tag_draft(PAN.ANCHOR, u['uid'])}.json") for u in us]
+ # ⚠ R0 сторожится наравне с панелью. Прежняя редакция шла по `survivors()` = T1..T3, и строка
+ # боевого редактора — та, против которой читается ВЕСЬ вывод о цене, — не проверялась ничем.
+ arms = [("R0", [cost(T22 / f"{JU.P22.tag_edit(PAN.ANCHOR, u['uid'])}.json") for u in us])]
+ arms += [(f"T{i}", [cost(MONEY.OUT / f"{PAN.tag_c(m, u['uid'])}.json") for u in us])
+ for i, m in enumerate(PAN.survivors(), start=1)]
+ for name, cs in arms:
+ nz = [c for c in cs if c > 0]
+ if not nz:
+ continue
+ ck(f"{name}: p50 редактуры {sst.median(nz):.5f}", has(t, f"{sst.median(nz):.5f}"))
+ link = [c + b for c, b in zip(cs, base, strict=True) if c > 0 and b > 0]
+ if link:
+ ck(f"{name}: книга по СВЯЗКЕ ${sst.median(link) * 1500:.2f}",
+ has(t, f"{sst.median(link) * 1500:.2f}"))
+
+ print("\n=== СВЕЖЕСТЬ РАЗБОРА ===")
+ # ⚠ ГОЛОС СТАРШЕ СВОЕГО ОТВЕТА = РАЗБОР ГНАЛСЯ В СЕРЕДИНЕ ГОНКИ. Поймано аудитом: два круга
+ # досуживания писали в ОДНИ И ТЕ ЖЕ файлы ответов, `--ingest` прошёл между ними, и 5 единиц из
+ # 32 остались с голосами от одного круга при ответах от другого. Числа при этом пере-считывались
+ # из голосов и сходились с отчётом — то есть ни один прежний гейт этого не видел.
+ for pass_ in JU.PASSES:
+ votes, answ = MONEY.OUT / f"aj-{pass_}-votes", MONEY.OUT / f"aj-{pass_}"
+ if not votes.exists() or not answ.exists():
+ continue
+ stale = [a.stem for a in answ.glob("*.txt")
+ if (votes / f"{a.stem}.json").exists()
+ and a.stat().st_mtime > (votes / f"{a.stem}.json").stat().st_mtime + 1]
+ ck(f"{pass_}: голоса не старше ответов судей", not stale,
+ f"устарели {len(stale)}: {' '.join(sorted(stale)[:6])}" if stale else "")
+
+ print("\n=== СУДЕЙСТВО ===")
+ for pass_ in JU.PASSES:
+ v = MONEY.OUT / f"aj-{pass_}-votes"
+ # ⚠ ПРОХОД БЕЗ ГОЛОСОВ РОНЯЕТ ГЕЙТ. Прежняя редакция делала `continue`, и гейт печатал
+ # «ВСЕ ЧИСЛА ОТЧЁТА СХОДЯТСЯ» с кодом 0, не проверив по проходу НИ ОДНОГО числа, — при
+ # том что отчёт печатал по нему несущий вывод. Ровно этим прошёл аннулированный border.
+ if not v.exists() or not list(v.glob("*.json")):
+ ck(f"{pass_}: голоса прохода на диске", False, "нет ни одного — числа не пере-снимаемы")
+ continue
+ JU.setup(pass_)
+ import absjudge as AJ # noqa: PLC0415
+ data = AJ._by_unit() # noqa: SLF001
+ ck(f"{pass_}: единиц отсужено {len(data)}", len(data) >= 3, f"{len(data)}")
+ for a, b, _w in JU.family(pass_):
+ m = AJ._margins(a, b) # noqa: SLF001
+ if len(m) < 3:
+ continue
+ ck(f"{pass_}: контраст {a} vs {b} = {sst.mean(m):+.2f} напечатан",
+ has(t, f"{sst.mean(m):+.2f}") or has(t, f"{sst.mean(m):.2f}"))
+ lo, hi = AJ.BO.boot_ci(m)
+ ck(f"{pass_}: ДИ {a} vs {b} [{lo:+.2f}, {hi:+.2f}] напечатан",
+ has(t, f"[{lo:+.2f},{hi:+.2f}]"))
+ dec = [d[d["CTRLdecoy"].get("decoy_from", "")]["errors"] - d["CTRLdecoy"]["errors"]
+ for d in data.values()
+ if "CTRLdecoy" in d and d["CTRLdecoy"].get("decoy_from", "") in d]
+ # Декой — условие осмысленности пака по его же §0, поэтому сторожится ЧИСЛОМ, а не словом.
+ ck(f"{pass_}: ДЕКОЙ есть в каждой единице", len(dec) == len(data), f"{len(dec)}/{len(data)}")
+ if dec:
+ ck(f"{pass_}: декой пойман {sum(1 for x in dec if x < 0)}/{len(dec)}",
+ all(x < 0 for x in dec))
+ ck(f"{pass_}: величина декоя {sst.mean(dec):+.2f} напечатана",
+ has(t, f"{sst.mean(dec):+.2f}"))
+ flo = [d["CTRLfloorA"]["errors"] - d["CTRLfloorB"]["errors"]
+ for d in data.values() if "CTRLfloorA" in d and "CTRLfloorB" in d]
+ if flo:
+ sd = sst.pstdev(flo)
+ thr = 2.8 * sd / (len(flo) ** 0.5)
+ # Порог различимости — решающее правило пака: печатать его надо ИМЕННО из своего пола.
+ ck(f"{pass_}: ПОРОГ {thr:.2f} напечатан", has(t, f"{thr:.2f}"))
+ ck(f"{pass_}: пол {sst.mean(flo):+.2f} напечатан", has(t, f"{sst.mean(flo):+.2f}"))
+ # ⚠ НАКЛОН СТОРОЖИТСЯ ЧИСЛОМ (поправка фазы Д, чек-лист приёмки №16 п.6). В теле стояло
+ # +0.646, а текущее сырьё даёт +0.708: величина, на которую делается ПОПРАВКА боевого
+ # контраста, печаталась не из тех голосов, из которых считается сам контраст. Ни один
+ # прежний гейт наклон не трогал — сторожились только перевес, ДИ, пол, порог и декой.
+ xs, ys = [], []
+ for d in data.values():
+ mx = sst.mean([r["slot"] for r in d.values()])
+ my = sst.mean([r["errors"] for r in d.values()])
+ for r in d.values():
+ xs.append(r["slot"] - mx)
+ ys.append(r["errors"] - my)
+ if sum(x * x for x in xs) > 0:
+ slope = sum(a * b for a, b in zip(xs, ys, strict=True)) / sum(x * x for x in xs)
+ ck(f"{pass_}: НАКЛОН {slope:+.3f} напечатан", has(t, f"{slope:+.3f}"),
+ f"n={len(xs)}")
+
+ print("\n=== ДЕНЬГИ ===")
+ tot = 0.0
+ for ph in MONEY.TAGS:
+ sp = MONEY.Guarded(ph).spent()
+ tot += sp
+ if sp <= 0:
+ continue
+ ck(f"{ph}: расход ${sp:.6f} напечатан", has(t, f"{sp:.6f}"))
+ if sp > MONEY.CEILINGS[ph] + 1e-9:
+ ck(f"{ph}: ПРОБОЙ объявлен", has(t, "ПРОБОЙ") or has(t, "пробой"))
+ ck(f"расход пака ${tot:.6f} напечатан", has(t, f"{tot:.6f}"))
+ ck("подъём потолка фазы C из резерва объявлен", has(t, "2.30") and has(t, "резерв"))
+
+ print(f"\n{'ВСЕ ЧИСЛА ОТЧЁТА СХОДЯТСЯ' if not BAD else f'РАСХОЖДЕНИЙ: {BAD} — НЕ СДАЁТСЯ'}")
+ return BAD
+
+
+if __name__ == "__main__":
+ sys.exit(1 if main() else 0)
diff --git a/eval/role_topology/absjudge.py b/eval/role_topology/absjudge.py
index 362c9a1e..bd453271 100644
--- a/eval/role_topology/absjudge.py
+++ b/eval/role_topology/absjudge.py
@@ -60,7 +60,15 @@ KEYS = OUT / "blind-keys"
def _dirs():
"""Каталоги прохода. ВТОРОЙ проход обязан лежать отдельно: он и есть репликация,
- и затирание первого превратило бы её в пере-запись."""
+ и затирание первого превратило бы её в пере-запись.
+
+ ⚠ `DIRS_HOOK` заведён приёмкой №16 (чек-лист фазы Д, п.9). Репликацию пака 23 гоняли,
+ КОПИРУЯ каталоги руками, потому что имя каталога выводилось только из `PASS`: разбор шёл под
+ `PASS='border'` и штамповал голоса репликации именами судей БОЕВОГО прогона. Крючок даёт
+ прогону собственные каталоги, не трогая имя прохода (а с ним — ключ, соль и метки).
+ """
+ if DIRS_HOOK is not None:
+ return DIRS_HOOK()
return (OUT / f"aj-{PASS}-tasks", OUT / f"aj-{PASS}", OUT / f"aj-{PASS}-votes")
# ⚠ СОЛЬ ЖИВЁТ ВНЕ РЕПОЗИТОРИЯ. Адверсариальное ревью 07.08 восстановило 138 меток из 138, не
# открывая ключ: `layout()` — чистая функция от соли и `uid = sha1(source)`, а источник лежит В
@@ -100,6 +108,8 @@ UNITS_HOOK = None # callable() -> list[dict]
FLOOR_HOOK = None # callable(unit) -> tuple[str, str] | None — пара повторов одного арма
DECOY_HOOK = None # callable(slots: dict) -> str — чистый текст, в который сажается порча
SRC_LANG = "китайском" # как называется язык исходника в задании судье
+DIRS_HOOK = None # callable() -> (tasks, answers, votes) — свои каталоги прогона
+JUDGES_HOOK = None # callable(token, answer_mtime) -> str — кто судил ЭТОТ ответ
def configure(**kw) -> None:
@@ -141,9 +151,15 @@ FORMAT_HEAD = """Оцени КАЖДЫЙ вариант ОТДЕЛЬНО про
Ответ ЗАПИШИ ФАЙЛОМ в {path} — ровно в таком виде и ничем больше, блок на каждую метку:
{skeleton}
-ПОЧЕМУ обязательно для КАЖДОЙ ненулевой оси: короткая ЦИТАТА из текста через « | ».
-Число без цитаты не принимается и на диск не попадает: именно из-за отсутствия цитат показания
-прошлых судей оказались непроверяемыми. Ноль по оси обоснования не требует.
+ПОЧЕМУ обязательно для КАЖДОЙ ненулевой оси, и ось надо НАЗВАТЬ. Единственный принимаемый вид —
+имя оси заглавными, двоеточие, цитаты в «ёлочках» через « | »; перечисляй только ненулевые оси:
+
+ Т1-ПОЧЕМУ: ВЕРНОСТЬ: «цитата» | «ещё цитата». ЯЗЫК: «цитата». ФОРМА: «цитата».
+
+Разбор ищет имя оси буквально. Цитаты без имени оси не принимаются, и единица выбрасывается
+ЦЕЛИКОМ — вся работа по ней пропадает; так уже терялась треть пачки в трёх прогонах подряд, потому
+что это требование стояло в приёмке, но не в задании. Ноль по оси обоснования не требует; нечем
+подпереть число цитатой — ставь ноль, а не число без цитаты.
"""
@@ -293,8 +309,19 @@ def ingest() -> None:
# ⚠ ИДЕНТИЧНОСТЬ СУДЬИ. Первая версия рига её не персистировала вовсе, и по диску нельзя было
# ни посчитать согласие судей, ни разложить вердикты по семействам (требование промта стр.79).
# Поймано адверсариальным ревью 07.08. Карта раздачи лежит рядом с ключом и судье не даётся.
+ # ⚠ ПЛОСКАЯ КАРТА ПО ИМЕНИ ПРОХОДА НЕ РАЗЛИЧАЕТ ПРОГОНЫ (приёмка №16, чек-лист Д8 п.9).
+ # Репликация пака 23 судила ТЕ ЖЕ 32 токена вторым жребием судей, а карта лежит одна на
+ # проход — все 32 голоса репликации получили имена судей БОЕВОГО прогона. Перевесы от
+ # этого не двигаются (имя судьи в разность не входит), но «согласие судей» и разложение
+ # дисперсии по сессиям считались бы по ложной раскладке. Поэтому имя берётся крючком,
+ # который знает про ПРОГОН и про время ответа; плоская карта осталась дефолтом эксп-21/22.
jf = KEYS / f"{PASS}-JUDGES.json"
judges = json.loads(jf.read_text(encoding="utf-8")) if jf.exists() else {}
+
+ def judge_of(stem: str, path: Path) -> str:
+ if JUDGES_HOOK is not None:
+ return JUDGES_HOOK(stem, path.stat().st_mtime)
+ return judges.get(stem, "?")
VOTES.mkdir(parents=True, exist_ok=True)
ok = bad = 0
reasons: dict[str, int] = {}
@@ -352,7 +379,7 @@ def ingest() -> None:
fail(f"блок {lab} задан дважды: {'/'.join(dup)}", f.stem)
break
rows[lab] = dict(**meta, **ax, why=why, errors=sum(ax.values()),
- slot=int(lab[1:]), judge=judges.get(f.stem, "?"))
+ slot=int(lab[1:]), judge=judge_of(f.stem, f))
else:
(VOTES / f"{f.stem}.json").write_text(
json.dumps(rows, ensure_ascii=False), encoding="utf-8")
@@ -363,7 +390,10 @@ def ingest() -> None:
def _by_unit() -> dict[str, dict[str, dict]]:
VOTES = _dirs()[2]
out = {}
- for f in VOTES.glob("*.json"):
+ # ⚠ sorted, а не голый glob: `boot_ci` ресемплит по ПОЗИЦИЯМ списка, поэтому от порядка обхода
+ # каталога зависели границы доверительных интервалов — точечные оценки и p воспроизводились, а
+ # интервалы нет. Поймано приёмкой пака 23.
+ for f in sorted(VOTES.glob("*.json")):
rows = json.loads(f.read_text(encoding="utf-8"))
uid = next(iter(rows.values()))["uid"]
out[uid] = {r["arm"]: r for r in rows.values()}
diff --git a/eval/role_topology/bakeoff.py b/eval/role_topology/bakeoff.py
index 3b7756b7..a1738e45 100644
--- a/eval/role_topology/bakeoff.py
+++ b/eval/role_topology/bakeoff.py
@@ -482,19 +482,50 @@ def boot_ci(xs: list[float], reps: int = 5000, alpha: float = 0.05) -> tuple[flo
def sign_perm_p(xs: list[float]) -> float:
- """Точный двусторонний знаковый перестановочный тест: перебираются ВСЕ 2^n расстановок знаков.
+ """ТОЧНЫЙ двусторонний знаковый перестановочный тест. $0.
Заведён 07.08 по адверсариальному ревью. Bootstrap-ДИ отвечает на вопрос «где лежит среднее»,
а не «отличимо ли оно от нуля при этом n», и на 15 единицах даёт заметно более узкий интервал,
чем точный тест. Здесь считается p, к которому применима поправка Holm по семейству контрастов.
"""
n = len(xs)
- if n == 0 or n > 20:
+ if n == 0:
return float("nan")
obs = abs(sum(xs))
- hits = sum(1 for mask in range(1 << n)
- if abs(sum(x if mask >> i & 1 else -x for i, x in enumerate(xs))) >= obs - 1e-12)
- return hits / (1 << n)
+ if n <= 20:
+ hits = sum(1 for mask in range(1 << n)
+ if abs(sum(x if mask >> i & 1 else -x for i, x in enumerate(xs))) >= obs - 1e-12)
+ return hits / (1 << n)
+ # ⚠ n > 20: ПОЛНЫЙ ПЕРЕБОР 2^n НЕПОДЪЁМЕН, НО ТОЧНЫЙ ОТВЕТ ВСЁ РАВНО ЕСТЬ. Первая редакция
+ # брала здесь Монте-Карло с фиксированным зерном; ревью исполнением показало, что этого не
+ # нужно: перевесы — ЦЕЛЫЕ числа (разности счётчиков ошибок), поэтому распределение суммы ±xᵢ
+ # считается динамикой по достижимым суммам за миллисекунды и ТОЧНО. Цена приближения была
+ # видна в отчёте: одна и та же величина печаталась как 0.1603 и 0.1608 (se Монте-Карло
+ # ≈0.0008), то есть заявление «числа воспроизводятся до знака» на ней не выполнялось.
+ # Дробные значения (их у нас нет, но контракт функции шире) уходят на прежний Монте-Карло.
+ scaled = [x * 2 for x in xs] # полуцелые тоже берём точно
+ if all(abs(v - round(v)) < 1e-9 for v in scaled):
+ ints = [abs(int(round(v))) for v in scaled]
+ target = abs(sum(scaled)) - 1e-9
+ # dp[s] = число расстановок знаков, дающих сумму s (сдвиг на total для неотрицательности)
+ total = sum(ints)
+ dp = [0] * (2 * total + 1)
+ dp[total] = 1
+ for v in ints:
+ nxt = [0] * (2 * total + 1)
+ for s, c in enumerate(dp):
+ if c:
+ nxt[s + v] += c
+ nxt[s - v] += c
+ dp = nxt
+ hits = sum(c for s, c in enumerate(dp) if abs(s - total) >= target)
+ return hits / (1 << len(xs))
+ import random # noqa: PLC0415
+ rnd = random.Random(20260809) # noqa: S311
+ trials = 200_000
+ hits = sum(1 for _ in range(trials)
+ if abs(sum(x if rnd.getrandbits(1) else -x for x in xs)) >= obs - 1e-12)
+ return (hits + 1) / (trials + 1)
def cmd_score() -> None:
diff --git a/eval/tenant_panel/bank.py b/eval/tenant_panel/bank.py
index 8422aa89..52b7b519 100644
--- a/eval/tenant_panel/bank.py
+++ b/eval/tenant_panel/bank.py
@@ -139,6 +139,14 @@ def _rx(dst: str) -> str:
влияет), объявляется как девиация. Первая версия усекала только слова от 6 знаков, и короткие
склоняемые слова канона («род Цинь») не находились в форме «рода Цинь» — метрика покрытия
занижалась бы на всех армах одинаково, но занижалась.
+
+ ⚠⚠ ГРАНИЦА СЛОВА В НАЧАЛЕ — поправка фазы Д (адверсариальное ревью 10.08). Без неё усечённый
+ корень ловился В СЕРЕДИНЕ чужого слова: канон «род Цинь» матчился на «Го-РОд Цинчжоу» и
+ «Б-РОвь Цинь», канон «род Су» — на «Пилюлю Кровавой Су». Замер: **30 ложных попаданий из
+ 2113 = 1.4%**; кросс-термовых коллизий (одна форма подходит под ДВА канона) нет ни одной.
+ Влияние на печатные числа: покрытие всех армов падает на 0.003–0.004 — то есть В ОДНУ сторону
+ у всех, и ни один относительный вывод эксп-22 не двигается. Числа в отчёте пере-сняты.
+ `(? str:
parts.append(w[:-1] + r"\w*")
else:
parts.append(w)
- return r"\s+".join(parts)
+ return r"(? None:
f"{f'{k}/{s}':>14s}{(k / s if s else 0):>10.3f}")
+def battery_canon_f3() -> None:
+ """Та же батарея по армам Ф3. Прежде §6.2 отчёта считалась ВНЕ дерева: шапка обещала, что
+ пере-считывается каждое число, а таблица Ф3 под пере-счёт не попадала — клейм был шире гейта."""
+ h("Ф3 БАТАРЕЯ И ПОКРЫТИЕ КАНОНА ПО АРМАМ РЕДАКТОРОВ (детерминированно, шума нет)")
+ import editors as ED # noqa: PLC0415
+ zh, cards = M.units_zh(), B.load_cards()
+ print(f" {'арм':30s}{'ед.':>5s}{'ханьцзы':>9s}{'типогр':>8s}{'потеря':>8s}"
+ f"{'канон':>14s}{'покрытие':>10s}{'$/ед':>10s}")
+ pan = ED.panel()
+ for i, model in enumerate(pan):
+ arm = f"R{i}"
+ rows, k, s, costs = [], 0, 0, []
+ for u in zh:
+ t = ED.text_of(arm, u["uid"])
+ if not t.strip():
+ continue
+ rows.append(B.run_unit(B.Unit(source=u["source"], draft=t, final=t, cards=cards)))
+ kk, ss = BANK.coverage(u["source"], t)
+ k += kk
+ s += ss
+ f = OUT / f"{ED.tag(model, u['uid'])}.json"
+ if f.exists():
+ costs.append(json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0))
+ if rows:
+ print(f" {arm + ' ' + model:30s}{len(rows):5d}"
+ f"{sum(r['cjk_leak']['han_chars'] for r in rows):9d}"
+ f"{sum(r['typography']['violations'] for r in rows):8d}"
+ f"{sum(r['numbers']['lost_n'] for r in rows):8d}"
+ f"{f'{k}/{s}':>14s}{(k / s if s else 0):>10.3f}"
+ f"{(st.median(costs) if costs else 0):10.5f}")
+ print(" ⚠ Клетки с ПУСТЫМ содержимым в счёт не идут — именно поэтому у части армов ед. < 16;")
+ print(" оплаченные пустые клетки перечислены в блоке ДЕНЬГИ.")
+
+
+def _p95(xs: list[float]) -> float:
+ """p95 по ЕДИНИЦАМ, ближайший ранг. При n=16 это 16-я по возрастанию, то есть максимум:
+ честнее так и сказать, чем интерполяцией изображать точность, которой при таком n нет."""
+ if not xs:
+ return 0.0
+ s = sorted(xs)
+ return s[min(len(s) - 1, math.ceil(0.95 * len(s)) - 1)]
+
+
def economics() -> None:
- h("Ф2 (д) ЦЕНА/ЕДИНИЦА И ПРОЕКЦИЯ НА КНИГУ (1500 единиц)")
+ h("Ф2 (д) ЦЕНА/ЕДИНИЦА И ПРОЕКЦИЯ НА КНИГУ (1500 единиц) — p50 И p95, как заказано промтом")
zh = M.units_zh()
- print(f" {'жилец':26s}{'черновик':>11s}{'редактор':>11s}{'связка':>11s}{'книга $':>10s}")
+ print(f" {'жилец':26s}{'связка p50':>11s}{'связка p95':>11s}"
+ f"{'книга p50':>11s}{'книга p95':>11s}")
for m in P2.tenants():
dc = [P2.draft_cost(m, u["uid"]) for u in zh]
ec = []
@@ -123,11 +168,49 @@ def economics() -> None:
ec.append(json.loads(f.read_text(encoding="utf-8")).get("cost_usd", 0.0))
if not dc:
continue
- d50, e50 = st.median(dc), (st.median(ec) if ec else 0.0)
- print(f" {m:26s}{d50:11.5f}{e50:11.5f}{d50 + e50:11.5f}{(d50 + e50) * 1500:10.2f}")
+ # p50/p95 берутся от СВЯЗКИ по единице, а не складываются из отдельных квантилей ролей:
+ # сумма p95 двух ролей — не p95 суммы, и завышала бы хвост.
+ pair = [d + e for d, e in zip(dc, ec, strict=False)] if ec else dc
+ print(f" {m:26s}{st.median(pair):11.5f}{_p95(pair):11.5f}"
+ f"{st.median(pair) * 1500:11.2f}{_p95(pair) * 1500:11.2f}")
print(" ⚠ DeepSeek объявил на прайс-странице 08.08 повышение цен — проекция едет с оговоркой")
+def drafts_as_text() -> None:
+ h("Ф2 (а) ЧЕРНОВИК КАК ТЕКСТ — ОПИСАТЕЛЬНАЯ ОСЬ ЗАКАЗА (поправки не несёт, вердиктов не даёт)")
+ import statistics as sst # noqa: PLC0415
+ for pass_ in ("p2zh", "p2en"):
+ JU.setup(pass_)
+ import absjudge as AJ # noqa: PLC0415
+ if not AJ._by_unit(): # noqa: SLF001
+ continue
+ fl = {"p2zh": 2.03, "p2en": None}[pass_]
+ print(f" --- {pass_} --- порог различимости "
+ f"{('%.2f' % fl) if fl else 'своего пола нет (пол Ф2 снят на zh)'}")
+ for a, bb, _w in JU.family_desc(pass_):
+ m = AJ._margins(a, bb) # noqa: SLF001
+ if not m:
+ continue
+ mean = sst.mean(m)
+ mark = ("ниже порога различимости" if fl and abs(mean) < fl else
+ "выше порога" if fl else "")
+ print(f" {a} vs {bb} n={len(m):2d} {mean:+6.2f} {mark}")
+ print(" ⚠ Знак ПОЛОЖИТЕЛЬНЫЙ = черновик жильца лучше якорного. Ось описательная: пре-рег Ф2")
+ print(" сузил решающее семейство до финалов, поэтому Холм по ней НЕ считается.")
+
+
+def positional() -> None:
+ h("ПОЗИЦИОННЫЙ НАКЛОН — ЗАМЕРЕН И ВЫЧТЕН (требование ЗАКОНа промта)")
+ for pass_ in ("p2zh", "p2en", "p3", "dva"):
+ JU.setup(pass_)
+ import absjudge as AJ # noqa: PLC0415
+ if not AJ._by_unit(): # noqa: SLF001
+ continue
+ print(f" --- {pass_} ---")
+ AJ.position()
+ print()
+
+
def judged(pass_: str) -> None:
h(f"СУДЕЙСТВО, проход {pass_}")
JU.setup(pass_)
@@ -176,7 +259,10 @@ def money_two_ways() -> None:
if empty_paid:
print(f" оплачено за ПУСТОЙ выход: {len(empty_paid)} вызовов "
f"${sum(c for _n, c in empty_paid):.5f}")
- for n, c in sorted(empty_paid, key=lambda x: -x[1])[:5]:
+ tot_empty = sum(c for _n, c in empty_paid)
+ print(f" доля пака: {tot_empty / sum(led.values()) * 100:.1f}% "
+ f"(${tot_empty:.5f} из ${sum(led.values()):.6f})")
+ for n, c in sorted(empty_paid, key=lambda x: -x[1]):
print(f" {n} ${c:.5f}")
@@ -198,12 +284,15 @@ def main() -> None:
screen()
echo_rate()
battery_canon()
+ battery_canon_f3()
economics()
+ drafts_as_text()
for p in ("p2zh", "p2en", "p3", "dva"):
try:
judged(p)
except Exception as e: # noqa: BLE001
print(f"\n═══ СУДЕЙСТВО {p} ═══\n не считается: {type(e).__name__}: {e}")
+ positional()
money_two_ways()
agent_runs()
print("\nВСЁ ВЫШЕ ПЕРЕ-СНЯТО ИЗ СЫРЬЯ. Чисел без пути воспроизведения в отчёте быть не должно.")
diff --git a/eval/tenant_panel/judge.py b/eval/tenant_panel/judge.py
index d743e983..88aeb0fb 100644
--- a/eval/tenant_panel/judge.py
+++ b/eval/tenant_panel/judge.py
@@ -79,6 +79,22 @@ def family(pass_: str) -> list[tuple[str, str, str]]:
return [("D_", "A", "однопроходка без мандата против связки (сырьё эксп-21)")]
+def family_desc(pass_: str) -> list[tuple[str, str, str]]:
+ """ОПИСАТЕЛЬНОЕ семейство — ось (а) заказа: черновик КАК ТЕКСТ, до редактора.
+
+ Пре-рег Ф2 сузил РЕШАЮЩЕЕ семейство до финалов (решение владельца принимается по тому, что
+ уедет в книгу), и эти контрасты поправку Холма не несут и вердиктов не определяют. Но
+ печатать их обязательно: тот же пре-рег требует печатать перевес ниже пола ЧИСЛОМ, а не
+ замалчивать, а промт заказал эту ось явным пунктом. Молчание о купленном и отсуженном
+ замере — та самая ловушка «след отчёта», от которой пак и защищается.
+ """
+ if pass_ in ("p2zh", "p2en"):
+ ts = P2.tenants()
+ return [(f"D{i}", "D0", f"черновик жильца {i} КАК ТЕКСТ против якорного черновика")
+ for i in range(1, len(ts))]
+ return []
+
+
def arms(pass_: str) -> tuple[str, ...]:
return tuple(arm_map(pass_))
@@ -128,7 +144,9 @@ def decoy_base(slots: dict) -> str:
def setup(pass_: str) -> None:
- AJ.configure(OUT=OUT, KEYS=KEYS, PASS=pass_, ARMS=arms(pass_), ARMS_2B=(),
+ AJ.configure(DIRS_HOOK=None, JUDGES_HOOK=None, # крючки — глобали ОБЩЕГО рига:
+ # не сбросишь — соседний пак прочитает чужие каталоги молча (ревью 10.08)
+ OUT=OUT, KEYS=KEYS, PASS=pass_, ARMS=arms(pass_), ARMS_2B=(),
FAMILY=family(pass_), TEXT_HOOK=text_of(pass_),
UNITS_HOOK=lambda: units(pass_), FLOOR_HOOK=floor_of(pass_),
DECOY_HOOK=decoy_base,
diff --git a/eval/tenant_panel/sol.py b/eval/tenant_panel/sol.py
index 7d9c85b3..4b45b159 100644
--- a/eval/tenant_panel/sol.py
+++ b/eval/tenant_panel/sol.py
@@ -82,10 +82,49 @@ def pick_units(a: str, b: str) -> list[dict]:
return [u for _g, u in scored[:N_UNITS]]
+# ⚠ РАМКА ВНЕШНЕГО СУДЬИ. Sol работает в UI-подписке владельца, а не по API: у него нет ни ключа,
+# ни жёсткого формата ответа, ни запрета на веб. Владелец 09.08: «он пипец как любит посмотреть
+# туда, куда его не просят». Три риска, каждый обесценивает пакет: узнавание книги вместо сверки с
+# исходником (контаминация — тот самый класс, ради которого искали невиданный срез); собственный
+# вариант перевода вместо счёта ошибок; разметка и преамбулы, ломающие построчный разбор.
+FRAME = """⛔ РАМКА ЗАДАНИЯ — прочитай целиком до того, как начнёшь.
+
+Это слепой замер, а не консультация. От тебя нужен РОВНО ОДИН артефакт: блок строк с числами.
+Материал оплачен и одноразовый; всё, что сверх формата, ломает машинный разбор и обесценивает его.
+
+ЧЕГО ДЕЛАТЬ НЕЛЬЗЯ:
+* НЕ искать ничего вовне этого сообщения — ни в сети, ни в памяти: ни книгу, ни автора, ни главу,
+ ни чужие переводы. Если ты узнал произведение — не опирайся на это ни одним словом. Единственный
+ эталон — ИСХОДНИК ниже. Узнавание вместо сверки с исходником делает замер недействительным.
+* НЕ предлагать свой перевод, не переписывать, не редактировать и не «улучшать» варианты.
+* НЕ гадать, кто автор вариантов — человек, модель, какая именно, какой вариант «основной».
+ Порядок вариантов перемешан жребием и не значит ничего.
+* НЕ оценивать варианты друг через друга. Каждый считается ОТДЕЛЬНО против исходника: вопрос не
+ «какой лучше», а «сколько ошибок в этом».
+* НЕ добавлять заголовки, таблицы, markdown-разметку, жирный шрифт, преамбулы и рекомендации.
+* НЕ задавать уточняющих вопросов и не просить контекст: его не будет, отвечай по тому, что дано.
+* НЕ менять имена строк ответа и не переставлять их — разбор ищет их буквально, посимвольно."""
+
+FORMAT = """ФОРМАТ ОТВЕТА — РОВНО ЭТИ ДЕВЯТЬ СТРОК, КАЖДАЯ С НОВОЙ, БЕЗ РАЗМЕТКИ И БЕЗ ТЕКСТА МЕЖДУ НИМИ:
+В1-ВЕРНОСТЬ: <число>
+В1-ТЕРМИН: <число>
+В1-ЯЗЫК: <число>
+В1-ФОРМА: <число>
+В2-ВЕРНОСТЬ: <число>
+В2-ТЕРМИН: <число>
+В2-ЯЗЫК: <число>
+В2-ФОРМА: <число>
+СТИЛЬ: <В1 или В2 или НИЧЬЯ — где живее и естественнее русская проза>
+
+Пояснения и разбор можно дать, но ТОЛЬКО ПОСЛЕ этих девяти строк, отдельным абзацем. Ничего перед
+ними и ничего между ними. Если по какой-то оси ошибок нет — ставь 0, а не пропуск и не прочерк."""
+
+
def _packet(src: str, v1: str, v2: str, tok: str) -> str:
+ axes = J.RUBRIC.split("---", 1)[0].strip() if "---" in J.RUBRIC else J.RUBRIC.strip()
return "\n".join([
f"ПАКЕТ {tok}", "",
- J.RUBRIC.split("---", 1)[0].strip() if "---" in J.RUBRIC else J.RUBRIC.strip(), "",
+ FRAME, "", "ЧТО СЧИТАТЬ ОШИБКОЙ — по типам:", axes, "", FORMAT, "",
"=" * 60, "ИСХОДНИК:", src, "",
"=" * 60, "ВАРИАНТ 1:", v1, "",
"=" * 60, "ВАРИАНТ 2:", v2, ""])
@@ -129,6 +168,128 @@ def emit() -> None:
print(f"инструкция → {SOL}/ИНСТРУКЦИЯ.md")
+# ── ПАКЕТ (б): слепые пары ФИНАЛИСТОВ Ф2/Ф3 ────────────────────────────────────────────────
+# Заказан промтом «по готовности Ф2/Ф3». Смысл иной, чем у пакета (а): (а) арбитрирует спор
+# ДВУХ платных судей эксп-21, а (б) выносит наружу решение, которое внутри пака вынесла ОДНА
+# модельная семья. Все 40 судейских сессий — один и тот же судья в разных контекстах, и это
+# объявлено непереносимым (§11 отчёта). Sol — единственный контур другой семьи.
+PAIRS_B = [
+ # (проход, арм A, арм B, зачем)
+ ("p3", "R0", "R2", "редактор: рекомендованный против ПОГРАНИЧНОГО (на нём висит Резерв D39.22)"),
+ ("p2zh", "E0", "E2", "черновик: финал поверх якоря против финала поверх лучшего соперника"),
+]
+N_UNITS_B = 3
+
+
+def _no_openai(models: list[str]) -> None:
+ """Семейный запрет симметричен (ЗАКОН промта): Sol не судит клетки OpenAI-моделей."""
+ import roster as R # noqa: PLC0415
+ bad = [m for m in models if "openai.com" in R.CANDIDATES.get(m, ("",))[0]]
+ if bad:
+ raise SystemExit(f"семейный запрет: Sol не судит клетки OpenAI — {bad}")
+
+
+def pick_units_b(pass_: str, a: str, b: str) -> list[dict]:
+ """Единицы, где ВНУТРИСЕМЕЙНЫЙ судья был увереннее всего.
+
+ Для арбитража это правильнее, чем брать спорные: если внешний судья не воспроизводит перевес
+ там, где свой был максимально уверен, — под вопросом сам перевес, а не его величина.
+ """
+ import judge as JU # noqa: PLC0415
+ JU.setup(pass_)
+ import absjudge as AJ2 # noqa: PLC0415
+ per = []
+ for uid, d in AJ2._by_unit().items(): # noqa: SLF001
+ if a in d and b in d:
+ per.append((abs(d[b]["errors"] - d[a]["errors"]), uid))
+ per.sort(key=lambda x: (-x[0], x[1]))
+ byid = {u["uid"]: u for u in _units_b()}
+ return [byid[uid] for _g, uid in per[:N_UNITS_B] if uid in byid]
+
+
+def _units_b() -> list[dict]:
+ import material as M # noqa: PLC0415
+ return M.units_zh()
+
+
+def _text_b(pass_: str, arm: str, uid: str) -> str:
+ import judge as JU # noqa: PLC0415
+ return JU.text_of(pass_)(arm, {"uid": uid})
+
+
+def emit_b() -> None:
+ """Выпуск пакета (б). $0: ничего не покупается, только сборка уже купленных клеток."""
+ import judge as JU # noqa: PLC0415
+ out = MONEY.OUT / "sol-b"
+ out.mkdir(parents=True, exist_ok=True)
+ (out / "answers").mkdir(parents=True, exist_ok=True)
+ key: dict[str, dict] = {}
+ n = 0
+ for pass_, a, b, why in PAIRS_B:
+ _no_openai([JU.arm_map(pass_).get(a, ""), JU.arm_map(pass_).get(b, "")])
+ for u in pick_units_b(pass_, a, b):
+ ta, tb = _text_b(pass_, a, u["uid"]), _text_b(pass_, b, u["uid"])
+ if not ta.strip() or not tb.strip():
+ continue
+ for order in (0, 1):
+ tok = hashlib.sha1( # noqa: S324
+ f"{_salt()}|b|{pass_}|{a}|{b}|{u['uid']}|{order}".encode()).hexdigest()[:10]
+ v1, v2 = (ta, tb) if order == 0 else (tb, ta)
+ (out / f"{tok}.txt").write_text(_packet(u["source"], v1, v2, tok), encoding="utf-8")
+ key[tok] = dict(contrast=f"{a}v{b}", pass_=pass_, why=why,
+ first=a if order == 0 else b, second=b if order == 0 else a,
+ uid=u["uid"], order=order, kind="боевой")
+ n += 1
+ # тот же обязательный декой: сессия, не поймавшая посаженную деградацию, аннулируется
+ u0 = _units_b()[0]
+ clean = _text_b("p3", "R0", u0["uid"])
+ planted, k = AJ._plant(clean) # noqa: SLF001
+ if k >= 2:
+ for order in (0, 1):
+ tok = hashlib.sha1(f"{_salt()}|b-decoy|{u0['uid']}|{order}".encode()).hexdigest()[:10] # noqa: S324
+ v1, v2 = (clean, planted) if order == 0 else (planted, clean)
+ (out / f"{tok}.txt").write_text(_packet(u0["source"], v1, v2, tok), encoding="utf-8")
+ key[tok] = dict(contrast="ДЕКОЙ", first="чистый" if order == 0 else "порченый",
+ second="порченый" if order == 0 else "чистый", uid=u0["uid"],
+ order=order, kind="контроль-декой", plants=k)
+ n += 1
+ (KEYS / "sol-b-KEY.json").write_text(json.dumps(key, ensure_ascii=False, indent=1),
+ encoding="utf-8")
+ (out / "ИНСТРУКЦИЯ.md").write_text(_instr_b(len(key)), encoding="utf-8")
+ print(f"пакет (б): {n} файлов → {out} · ключ → {KEYS}/sol-b-KEY.json (владельцу НЕ давать)")
+
+
+def _instr_b(n: int) -> str:
+ return f"""# Пакеты для Sol — ПАКЕТ (б), финалисты эксп-22
+
+Всего пакетов: **{n}**. Правила те же, что в пакете (а): один пакет — один НОВЫЙ чат, ответ
+копируется дословно в `answers/<тот же токен>.txt`, порядок вариантов внутри пакета не менять,
+какая модель какой вариант писала — не сообщать.
+
+## Чем этот пакет отличается от (а)
+
+Пакет (а) разрешал спор двух платных судей эксп-21. Здесь спора нет — здесь есть **решение,
+которое целиком вынесла ОДНА модельная семья**: все 40 судейских сессий эксп-22 — один и тот же
+судья в разных контекстах. Он мог систематически предпочитать один стиль русской прозы другому,
+и изнутри пака это неотличимо от истины. Sol — единственный судейский контур другой семьи.
+
+Поэтому важны именно эти пары:
+
+* **редактор рекомендованный против пограничного** — на этом контрасте держится вопрос о
+ Резерве D39.22, и внутри пака он прошёл на грани (перевес 2.12 при пороге прохода 2.47);
+* **финал поверх якорного черновика против финала поверх лучшего соперника** — крупнейший
+ перевес фазы 2.
+
+Единицы отобраны те, где внутренний судья был увереннее всего: если внешний судья не
+воспроизводит перевес там, где свой был максимально уверен, под вопросом сам перевес.
+
+## Что будет с ответами
+
+Возврат не блокер: не вернётся — так и будет записано. Вернётся — числа лягут рядом с
+внутренними, и расхождение станет находкой, а не поводом переписать вывод.
+"""
+
+
def _instr(n: int) -> str:
return f"""# Пакеты для Sol — инструкция владельцу
@@ -241,5 +402,5 @@ def score() -> None:
if __name__ == "__main__":
a = sys.argv[1:] or ["--emit"]
- fn = {"--emit": emit, "--ingest": ingest, "--score": score}.get(a[0])
+ fn = {"--emit": emit, "--emit-b": emit_b, "--ingest": ingest, "--score": score}.get(a[0])
fn() if fn else print(__doc__)
diff --git a/eval/tenant_panel/verify22.py b/eval/tenant_panel/verify22.py
index 4ee0f80f..4a01d0e6 100644
--- a/eval/tenant_panel/verify22.py
+++ b/eval/tenant_panel/verify22.py
@@ -8,6 +8,12 @@
Правило: гейт сторожит НЕСУЩИЕ числа — те, на которых стоит решение владельца. Число, которого
гейт не проверяет, в ИТОГе стоять не должно.
+
+⚠ ЧЕГО ЭТОТ ГЕЙТ НЕ ДЕЛАЕТ, чтобы клейм не был шире прибора. Он проверяет, что число ПРИСУТСТВУЕТ
+в тексте отчёта, а не что оно стоит в нужном месте и не что предложение вокруг него истинно.
+Приёмка нашла ровно этот класс: «все перевесы выше порога 2.47» — все числа напечатаны и сходятся,
+а утверждение о них ложно. Пороговые и сравнительные предложения гейт не сторожит; их проверяет
+только чтение.
"""
from __future__ import annotations
@@ -36,9 +42,16 @@ def ck(name: str, ok: bool, detail: str = "") -> None:
print(f"[{'OK ' if ok else 'ПРОВАЛ'}] {name}" + (f" {detail}" if detail else ""))
+def _norm(x: str) -> str:
+ """Нормализация ПЕРЕД сравнением: пробелы не значимы, а типографский минус U+2212 и
+ ASCII-дефис — один и тот же знак. ⚠ Второе заведено гейтом-же: он пометил девять расхождений
+ там, где число совпадало, а отличался ЗНАК-СИМВОЛ. Различать их — ложная тревога, но молчать
+ о них нельзя, поэтому нормализуется знак, а не ослабляется проверка."""
+ return re.sub(r"\s+", "", x).replace("\u2212", "-").replace("\u2013", "-")
+
+
def has(text: str, s: str) -> bool:
- """Число ищется по НОРМАЛИЗОВАННОМУ тексту: пробелы и переносы строк не значимы."""
- return re.sub(r"\s+", "", s) in re.sub(r"\s+", "", text)
+ return _norm(s) in _norm(text)
def main() -> int:
@@ -79,6 +92,25 @@ def main() -> int:
for m in P2.tenants():
ck(f"жилец {m} назван в отчёте", has(t, m))
+ # ⚠ ПОКРЫТИЕ КАНОНА СТОРОЖИТСЯ ЧИСЛОМ (поправка фазы Д). Таблицы (г) и §6.2 несут по 18
+ # чисел покрытия, и до сих пор их не проверял никто: правка регекса банка прошла бы молча.
+ # Поймано ревью, когда в `bank._rx` нашлась ловля корня В СЕРЕДИНЕ чужого слова.
+ print("\n=== ПОКРЫТИЕ КАНОНА ===")
+ us = M.units_zh()
+ for label, get in (("D0", lambda u: P2.draft_text(P2.ANCHOR, u["uid"])),
+ ("E0", lambda u: P2.edit_text(P2.ANCHOR, u["uid"]))):
+ k = s = 0
+ for u in us:
+ o = get(u) or ""
+ if not o:
+ continue
+ a, b = BANK.coverage(u["source"], o)
+ k += a
+ s += b
+ if s:
+ ck(f"покрытие канона {label} = {k / s:.3f} напечатано", has(t, f"{k / s:.3f}"),
+ f"{k}/{s}")
+
print("\n=== ДЕНЬГИ ===")
for ph in ("Ф0", "Ф1", "Ф2", "Ф3"):
sp = MONEY.Guarded(ph).spent()
@@ -92,6 +124,66 @@ def main() -> int:
pack = MONEY.Pack().spent()
ck(f"расход пака ${pack:.6f} напечатан", has(t, f"{pack:.6f}"))
+ print("\n=== ОСЬ (а): ЧЕРНОВИК КАК ТЕКСТ ===")
+ import statistics as sst # noqa: PLC0415
+ for pass_ in ("p2zh", "p2en"):
+ votes = MONEY.OUT / f"aj-{pass_}-votes"
+ if not votes.exists() or not list(votes.glob("*.json")):
+ continue
+ import judge as JU0 # noqa: PLC0415
+ JU0.setup(pass_)
+ import absjudge as AJ0 # noqa: PLC0415
+ for a, bb, _w in JU0.family_desc(pass_):
+ m = AJ0._margins(a, bb) # noqa: SLF001
+ if not m:
+ continue
+ if pass_ == "p2zh": # решающая печать — zh; en описателен и в ИТОГ не вынесен
+ ck(f"{pass_}: {a} vs {bb} = {sst.mean(m):+.2f} напечатан",
+ has(t, f"{sst.mean(m):+.2f}"))
+
+ print("\n=== ПОЗИЦИОННЫЙ НАКЛОН (требование ЗАКОНа промта) ===")
+ ck("наклон объявлен замеренным по всем проходам", has(t, "0.078") and has(t, "0.469"))
+ ck("поправленное значение довеска напечатано", has(t, "-1.65") or has(t, "−1.65"))
+
+ print("\n=== ЦЕНА p95 ===")
+ import itog22 as IT # noqa: PLC0415
+ zh = M.units_zh()
+ for m in P2.tenants():
+ dc = [P2.draft_cost(m, u["uid"]) for u in zh]
+ ec = []
+ for u in zh:
+ f2 = MONEY.OUT / f"{P2.tag_edit(m, u['uid'])}.json"
+ if f2.exists():
+ ec.append(json.loads(f2.read_text(encoding="utf-8")).get("cost_usd", 0.0))
+ if not dc:
+ continue
+ pair = [d + e for d, e in zip(dc, ec, strict=False)] if ec else dc
+ ck(f"{m}: связка p95 {IT._p95(pair):.5f}", has(t, f"{IT._p95(pair):.5f}")) # noqa: SLF001
+
+ print("\n=== ПУСТОЙ ВЫХОД И ГРАНИЦА ДВОЙНОЙ ОПЛАТЫ ===")
+ empty = []
+ for globs in MONEY.TAGS.values():
+ for g in globs:
+ for f3 in MONEY.OUT.glob(g):
+ r3 = json.loads(f3.read_text(encoding="utf-8"))
+ if r3.get("cost_usd", 0) > 0 and not (r3.get("content") or "").strip():
+ empty.append(r3["cost_usd"])
+ if empty:
+ ck(f"оплачено за пустой выход ${sum(empty):.5f} напечатано", has(t, f"{sum(empty):.5f}"))
+ ck(f"{len(empty)} пустых вызовов объявлено", has(t, f"{len(empty)} вызов"))
+ # граница двойной оплаты обязана быть ВЫВОДИМОЙ: расход Ф2 до атомарного замка
+ import subprocess # noqa: PLC0415
+ at = subprocess.run(["git", "log", "--format=%at", "-1", "fd3e522"],
+ capture_output=True, text=True, cwd=REPO).stdout.strip()
+ if at:
+ pre = 0.0
+ for g in MONEY.TAGS["Ф2"]:
+ for f4 in MONEY.OUT.glob(g):
+ if f4.stat().st_mtime < int(at):
+ pre += json.loads(f4.read_text(encoding="utf-8")).get("cost_usd", 0.0)
+ ck(f"верхняя граница двойной оплаты ${pre:.6f} напечатана", has(t, f"{pre:.6f}"),
+ "граница = расход Ф2 ДО атомарного замка, в худшем случае оплаченный дважды")
+
print("\n=== СУДЕЙСТВО ===")
import judge as JU # noqa: PLC0415
rs = JU.runs_used()
|