textmachine/eval/dovodka/prereg-4axes/PREREG.md

361 lines
39 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ПРЕ-РЕГИСТРАЦИЯ СТЕНДА — контролируемая проба редактора по четырём осям
**Дата: 02.09.2026. Ветка `polygon`. Сессия-исполнитель `textmachine-2f`.**
**Закон: `eval/dovodka/PLAN-01-09.md`** (фриз ДОКУМЕНТА — коммит `dc602e9`).
Промт исполнителя: `eval/dovodka/PROMPT-PROBE-4AXES.md`.
> Настоящий коммит — **ФРИЗ №1: СТЕНД**, и он ложится **ДО первой платной клетки**. `dc602e9` заморозил
> дизайн, модели, правило решения, потолок, рамку и сид; здесь замораживается всё остальное, что после
> первой покупки править уже нельзя: конфиги, скрипты, sha256 и правило кассы.
> **ФРИЗ №2** — пин сигнатуры Δ`prompt_tokens`, снятой пред-полётом (план §4: полосу нельзя было задать
> заранее). Оба объявляются владельцу вслух.
Машинная копия всего перечисленного — `prereg.json` рядом (её читает `chetyre.py --manifest`).
Замороженные копии конфигов и промптов — `stand/`. Они лежат в зоне полигона, потому что физически
конфиги живут в каталоге книги, а `books/`**отдельный репозиторий**, и «фриз коммитом» там
неисполним по канону (план §5.5).
---
## 1. РАМКА ВЫБОРКИ — пинится первой, иначе «20 юнитов» читаются как ОТБОР
| что | значение |
|---|---|
| главы книги | **2642** (17 глав) |
| нумерация в движке | **117** в колонке `chapter` БД ⚠ движок не умеет смещение глав |
| файл среза | `~/books/gu-zhenren/probe-4axes/guzhenren-ch26-42.gb18030.txt` |
| sha256 среза | `1ebcc9efb0fa5006548eba8599b58d4d5a44a201562271d0ff9f1eb9b803fccf` |
| размер | 97 408 Б GB18030 · CRLF · 17 заголовков · 47 519 знаков без переводов строк |
| резалка | `eval/dovodka/narezka.py --from 26 --to 42` (вход UTF-8, GB18030 на выходе) |
| **n юнитов** | **20** |
| каталог | **новый** `~/books/gu-zhenren/probe-4axes/` — не `coldrun-v16` (там три оплаченных edit-юнита) |
| отбора нет | все юниты диапазона в порядке движка; **юнит пред-полёта ВХОДИТ в n** |
**Как снято `n`, командой (`$0`, до первой покупки):**
```
$ ~/books/gu-zhenren/probe-4axes/bin/tmctl manifest --config ~/books/gu-zhenren/probe-4axes/book.yaml
chapters=17 units=20 chunks=35
source: 97408 bytes (sha256 1ebcc9efb0fa) · encoding gb18030 · zh→ru
```
**МИНА НУМЕРАЦИИ СЛАБЕЕ, ЧЕМ БОЯЛСЯ ПЛАН — и это тоже улика, а не мнение.** План писал, что
соответствие «главы книги 2642 ≡ главы среза 117» держится ТОЛЬКО записью в пре-реге. На деле у
движка есть СВОЙ носитель: чанкер разбирает китайский номер `第二十六节` и печатает в манифесте
заголовки **«Глава 26» … «Глава 42»** при позиционных `number` 1…17. То есть соответствие
восстанавливается из артефакта прогона, а не только из этого документа. Команда:
```
$ python -c "import json;m=json.load(open('.../guzhenren-probe4.db.manifest.json'));
print([(c['number'],c['heading'],c['units_total']) for c in m['chapters']])"
(1,'Глава 26',1) (2,'Глава 27',1) (3,'Глава 28',2) … (17,'Глава 42',1) Σ units = 20
```
Три главы (28, 37, 38) дают по два юнита, остальные по одному — отсюда 20, а не 17.
**ОТСТУПЛЕНИЕ ОТ ОЖИДАНИЯ ПЛАНА, названное до покупки:** план ждал ~24 юнита по пропорции холодного
прогона (14 на 10 глав). Фактически **20**. Это не нарушение — §6 плана прямо говорит, что вариант
задаётся ДИАПАЗОНОМ ГЛАВ, а «24» есть ожидание, а не цель. **Следствие для мощности объявляется здесь,
а не после чисел:** при σ=1.2 MDE растёт с ×2.02 (n=24) до **×2.16** (n=20).
## 2. СИД И ПОРЯДОК
Сид **`20260902`**. Порядок рук внутри юнита — `eval/dovodka/poryadok.py`, перестановка на sha256
(не `random`: он не обещает стабильности между версиями Python). Контракт: четыре руки креста —
перестановкой, **`glm-off` ВСЕГДА пятый**, чтобы при денежном гарде терялся референс, а не пара, на
которой стоит E1. Выпавший порядок всех 20 юнитов — в `prereg.json` (`порядок_рук`).
## 3. КРЕСТ И МОДЕЛИ — фиксированы
| рука | промпт | `stages[edit].reasoning` | модель | что уходит на провод |
|---|---|---|---|---|
| `p7-off` | `editor-p7.md` (июльский) | `"off"` | `deepseek-v4-pro` | ключа `reasoning_effort` НЕТ ⇒ вендор-дефолт |
| `p7-low` | тот же | `"low"` | `deepseek-v4-pro` | `reasoning_effort:"low"` |
| `p9-off` | `editor-p9.md` (сегодняшний) | `"off"` | `deepseek-v4-pro` | ключа нет |
| `p9-low` | тот же | `"low"` | `deepseek-v4-pro` | `low` |
| ⭐ `glm-off` | `editor-p9.md` | `"off"` | **`glm-5`** | `extra_body {thinking:{type:disabled}}`**референс, НЕ фактор креста** |
Черновик `deepseek-v4-flash`@`low`, хоп эскалации `deepseek-v4-pro`, терминолог и классификатор
`deepseek-v4-flash`@`low`**константы во всех пяти руках**. **Судья не вызывается.**
**Провод проверен по коду, не по памяти:** `capability.go:236-239` — ветка `ReasoningNone` пишет
`reasoning_effort` только если значение не пусто и не `"off"`; у `deepseek-v4-pro` в `models.yaml`
блока `capabilities.reasoning` нет вовсе ⇒ `off` = ключ не отправлен, `low` = отправлен буквально.
У `glm-5` `control: extra_body_disable`, `off_extra_body: {thinking:{type:disabled}}`у неё `off`
это НАСТОЯЩЕЕ выключение размышления.
**Различие двух промптов — ровно одна строка** (проверено `diff`): в `editor-p9.md` к правилу о
повторах добавлена оговорка про авторский рефрен. Метка `prompt_version` у обоих одна
(`v3-discourse-reflow`), поэтому ось берётся **по SHA**, а не по метке.
## 4. SHA256 — всё, что после этого коммита не правится
Полный список — `prereg.json` (`sha256`). Несущие:
```
1ebcc9efb0fa5006548eba8599b58d4d5a44a201562271d0ff9f1eb9b803fccf срез глав 26-42
5ed4ee7ed5bc6c48102798a55567fb6752da3309caff8005f8167f8133e792fc бинарь tmctl (main @ bd2077b)
21b7735009c25679abc42e018692d3fde93beb3146b157603b5d49acda81ee01 models.yaml (побайтно один в обоих деревьях)
2b84dee34b0861637a055fee60372d2553f85ffa4c1b535c1e781f5167e8c3a1 editor-p7.md (P₇, июльский)
1ad4544e564a9b4049231e79e961a9cbb85bc39de07c5ce72cdbc8d92cc68999 editor-p9.md (P₉, сегодняшний)
3d2a7123bafc22cc6f9f93c62d2fb4cce3bb4d311437f14cbec6411fe4b1221b pairs/zh-ru.yaml
```
**Бинарь НЕ ТОТ, что вёз холодный прогон** (`e6f872df…`, main @ `065d8ac`). Между ними уехали
`volume.go` (+118), `terminologist.go` (+102), `snapshotdiff.go` (новый), `rebill.go`, `status.go`;
в `stagerun.go` изменился ТОЛЬКО текст ошибки снапшот-гарда (проверено `git diff` — формула
`max_tokens` не тронута), а `snapshot.go`, `ledger.go` и `models.yaml` не тронуты вовсе. Пере-сборка
из `065d8ac` потребовала бы `checkout` в чужом рабочем дереве — запрещено каноном. Расхождение
названо здесь, а не скрыто.
## 5. КОЭФФИЦИЕНТ ВОССТАНОВЛЕНИЯ РАЗМЫШЛЕНИЯ
`R̂ = completion_tokens 0.3644 · len(сырой response_text попытки)`**0.3644 токена/знак**, запинено.
Границы (из плана §3, названы до покупки): CJK в выходе ломает оценку и завышает `R̂`; при `R̂ < ~700`
относительная ошибка 2070% и о величине таких клеток вывод не делается; берётся СЫРАЯ строка
попытки, не `sanitized_export`; типичная ошибка ~3%, худшая клетка 5.3%.
**У референса `glm-5` `R̂ = 0` ПО ПОСТРОЕНИЮ**, а не по формуле.
**Дыра прибора подтверждена на данных:** в базе холодного прогона `sum(reasoning_tokens>0) = 0` при
68 строках; положительный контроль — 38 строк с непустым `completion_tokens`.
## 6. ДЕНЬГИ — правило запинено ДО чисел
* **Санкция владельца: $9.50 в ПИКОВЫХ терминах.** Сессией не поднимается. Гард ⇒ стоп и вопрос.
* **Пояс базового прогона: `--ceiling-usd 0.60`.**
* **Потолок руки** `i` = `base_факт + w_i · (9.50 base_факт)`, веса пропорциональны ожидаемой цене
клетки (§6 плана: ∅ $0.09, low $0.05, референс $0.02):
| рука | `w` | смысл |
|---|---|---|
| `p7-off`, `p9-off` | 0.3000 каждая | дороже всех: без ручки думают больше |
| `p7-low`, `p9-low` | 0.1667 каждая | |
| `glm-off` | 0.0667 | референс, теряется первым |
Сумма весов **1.0000**, поэтому `base + Σ(потолок_i base) = $9.50 РОВНО` при любом фактическом
`base`. Это и есть механизм, которого у леджера нет: он считает потолок по `book_id`, а `spend`
лежит ВНУТРИ проектной базы, и каждая из пяти копий несёт базовый расход под тем же `book_id`.
* **Агрегат ведётся вручную после КАЖДОГО юнита:** `израсходовано = база + Σ(копия база)`.
* ⚠ `--ceiling-usd` сравнивается с **накопленным** расходом книги, а не с расходом прогона
(`cmd/tmctl/main.go:260`) ⇒ в него кладётся «база + доля», а не «доля».
* ⚠ `--ceiling-usd` перекрывает **только** `book_usd`; `day_usd` берётся из `book.yaml`
(`stagerun.go:496`) ⇒ дневной потолок задан в каждой руке как `book_usd + $1.00`.
**⭐ ПОРОГ, ОБЪЯВЛЕННЫЙ ДО ПРЕД-ПОЛЁТА, чтобы не выбирать его после чисел.** Цена клетки `low`
единственное число сметы без источника (движковых редакторских вызовов на `low` не существует).
В репозитории есть независимый замер, которого план не использовал: `docs/experiments/23-editor-tier.md:7139`,
арм `RNL` 30.08 на `low``$0.056695` за вызов, то есть ближе к «худшему» сценарию плана ($0.065),
чем к центральному ($0.04). ⇒ **Решение: пред-полётная клетка `low` ≤ $0.065 — иду дальше** (это
худший сценарий §6 плана, потолок его покрывает ценой потери 12 юнитов, что план называет штатным
исходом). **> $0.065 — СТОП и вопрос владельцу**, потому что выше в замороженной смете сценария нет.
## 7. ОПРЕДЕЛЕНИЕ «ДОСТАВЛЕНО» — объявляется ДО чисел, вместе с проверкой устойчивости
`delivered` меряется на **отгружаемом** тексте (`checkpoints.response_text` по `chunk_status.final_hash`;
`final_hash` может быть синтетическим `tm-sanitized-v1:<sha>` и тоже лежит в `checkpoints` — проверено).
* **delivered = 0** ⟺ диспозиция `skipped`, ИЛИ `final_hash` не резолвится, ИЛИ отгружаемый текст не
проходит $0-предикат: доля кириллицы среди букв < 0.99 либо в тексте есть CJK.
* **иначе** `delivered` = длина отгружаемого текста в знаках.
**Почему НЕ «flagged ⇒ 0»:** в холодном прогоне 1 из 3 редакторских юнитов `sanitizer_stripped`
(CJK-утечка, санитайзер её вырезал, текст ОТГРУЖЕН, 6048 знаков). По правилу «flagged 0» гард
«>20% пустых ⇒ прибор отказывает» сработал бы от санитайзера, а не от предмета замера. Флаги при этом
не теряются — они целиком идут в E5, разделённые по механизму (санитайзер / CJK / обрыв по длине).
**Строгий вариант (`flagged ⇒ 0`) считается ВСЕГДА и печатается рядом как заранее объявленная проверка
устойчивости.**
## 8. ПРАВИЛО РЕШЕНИЯ — из §4 плана, здесь не пересматривается
`p<0.05` двусторонне ⇒ эффект есть, величина = медиана Ходжеса–Лемана с 95% ДИ. Иначе ⇒
**«НИЖЕ РАЗРЕШЕНИЯ ЗАМЕРА»**, и это НЕ «эффекта нет». Полоса [0.8; 1.25] — **описательная**, решения
не несёт. Нули (ничьи от цензуры) СОХРАНЯЮТСЯ: `zero_method='pratt'` (умолчание `wilcox` их
выбрасывает — проверено исполнением на `scipy 1.18.0`). ДИ Ходжеса–Лемана написан руками: у
`wilcoxon` в этой версии интервала нет (проверено — у результата только `statistic`/`pvalue`/`count`/`index`).
Цензура на потолке — симметрично, по §4; величина под цензурой печатается со знаком **«≥»**.
## 9. ⛔ ОТСТУПЛЕНИЯ ОТ ЗАМОРОЖЕННОЙ ПРОЦЕДУРЫ — предъявляются, а не правятся молча
План §10 требует: «если исполнение покажет, что что-то из замороженного неверно — это НАХОДКА,
которая записывается и предъявляется». Ниже — все, найденные ДО первой покупки.
| # | § | что в плане | что на самом деле | что делаю |
|---|----|---|---|---|
| 1 | §9.2 | конфиги рук в `$B/arms/` | `LoadPair` ищет пар-слой в `<каталог PIPELINE-файла>/pairs/` (`config/pipeline.go:821`), а отсутствие файла = `(nil, nil)` **тихо** (`config/pair.go:55-64`). ⚠ **ПЕРВОНАЧАЛЬНАЯ ФОРМУЛИРОВКА ЭТОЙ СТРОКИ БЫЛА ПРЕУВЕЛИЧЕНА И СНЯТА — см. §9-КВАТЕР** | конфиги рук лежат в `$B/` рядом с единственным `$B/pairs/`; в `$B/arms/` только `.book.yaml` и `.db` — решение остаётся, но обосновано отсутствием дрейфа двух копий, а не опасностью |
| 2 | §9.3 | `--ceiling-usd 1.20` на базовый прогон | правило §9.0 п.1 — «чуть выше ожидания ЧЕРНОВОЙ волны»; ожидание, пере-снятое с леджера: не-edit расход $0.436110$0.214865 = $0.221245 на 28 174 знака ⇒ ×1.6866 = $0.373 + эскалация ≈ **$0.45**. Зазор при поясе 1.20 = $0.75; резервация одной edit-клетки ≈ 16000·3.96/1e6 + 5000·1.32/1e6 ≈ **$0.070** (⚠ расчёт, не замер: потолок вывода pro = флор 16000) ⇒ в зазор влезает **порядка десяти** редакторских юнитов, а купленная в базе редактура необратима | пояс **0.60**. Асимметрия в правильную сторону: перелёт стоит перекупки, недолёт — бесплатного resume (код 4, черновики целы). ⚠ Первая же резервация черновика замерена Ш1: **$0.012052** — оценка вчетверо выше фактической цены чанка ($0.0034 по холодному прогону), потому что леджер букирует пик |
| 3 | §9.3 | `rc=$?` после `\| tee` | в сниппете плана **нет `set -o pipefail`**, а без него `$?` возвращает код `tee` ⇒ проверка `[ $rc -eq 4 ]` **не сработает никогда**. Проверено исполнением: без `pipefail` `$?` = 0, с `pipefail` = 4, `${PIPESTATUS[0]}` = 4 в обоих случаях | `${PIPESTATUS[0]}` (правда независимо от `pipefail`) |
| 4 | §9.3 | останов цикла по `grep -q 'VOLUME CEILING:'` | строка печатается при `res.Volume != nil`, то есть на **КАЖДОМ** прогоне с `--max-units` (`cmd/tmctl/render.go:130`) ⇒ `break 2` рвал бы цикл после первой же клетки | цикл ограничен **`N=20` из пре-рега**, а не выводом; строка «never been delivered» лишь ПЕЧАТАЕТСЯ как признак «рука закончена». Останавливают цикл: код 4, код 3, неожиданный код, рассинхрон рук и агрегат $9.50 |
| 5 | §9.3 | `--config $B/arms/$arm.yaml` при том, что §9.2 зовёт `$arm.yaml` копией **pipeline** | `translate` грузит **book.yaml** | пять `book.yaml` рук, различающиеся ровно тремя полями (`pipeline`, `project_db`, `ceilings`) |
| 6 | §9.1 | `day_usd` не упомянут | `--ceiling-usd` его не перекрывает; наследованный из образца `day_usd: 1.00` остановил бы руку `∅` посреди пачки | `day_usd = book_usd + $1.00` в каждой руке |
| 7 | §5.3 | «мина вооружается ТУМБЛЕРОМ, а уровни эффорта её не вооружают» | `00-provider-quirks.md:123`: «`effort:"low"` **ПЕРЕ-ВООРУЖАЕТ** эхо-мину… защита деградирует НЕПРЕРЫВНО с эффортом»; :132 — off 4/20 против low 3/20, на N=20 неразличимо | формулировка снята как неверная; критерий E5 плана опасность уже несёт, поэтому дизайн не меняется |
| 8 | §4 | ковариатная модель `log R̂ ~ effort + prompt + log(len_draft) + (1\|unit)` как проверка устойчивости E1/E3 | **не может быть проверкой устойчивости E1/E3**: `len_draft` — величина УНИТ-УРОВНЕВАЯ (все четыре руки редактируют ОДИН черновик — это и есть блокировка трудности), а в сбалансированном внутри-юнитном дизайне юнит-уровневая ковариата НЕ МЕНЯЕТ оценок усилия и промпта ПО ПОСТРОЕНИЮ. Сам наклон длины при этом оценим — он считается отдельно | считаю руками обе величины, которые она дала бы: (1) МНК контраста по `log(len_draft)` — внутри-юнитная устойчивость; (2) МНК среднего по юниту `log R̂` по `log(len_draft)` — это и есть коэффициент длины в сбалансированном дизайне. `statsmodels` **не установлен**; ⚠ причина НЕ «нет сети» (сеть есть, колесо под cp314 существует — проверено), а вырожденность модели плюс риск правки общего venv при живых параллельных сессиях |
| 9 | §9.6 | «`n` = сколько получится» | `tmctl manifest` бесплатен и даёт `units_total` **до** первой покупки | `n = 20` запинено здесь |
| 10 | §9.3 | у рук флага `--verify-bank` нет | флаг оставлен и в руках **намеренно**: в копиях банк уже предъявлен базовым прогоном, `hasUnpresentedCluster` вернёт false (`mining.go:222-230`), и движок напечатает «every cluster already presented — continuing». Это $0-улика состояния банка и страховка: если рука вдруг соберёт НОВЫЙ кластер, она встанет кодом 3 громко, а не поедет с другим банком молча | флаг подаётся всем пяти рукам; отступление названо здесь |
## 9-КВАТЕР. ⛔ МОЯ СОБСТВЕННАЯ ОШИБКА, СНЯТАЯ ИСПОЛНЕНИЕМ ДО ФРИЗА
**Класс: чтение кода — своё и агента-контролёра — принято за факт о мире.** Ровно тот класс, которым
предыдущая сессия ошиблась одиннадцать раз (§8 плана). Записано здесь, потому что фриз не должен
нести преувеличенную находку.
**Что я утверждала:** «руки в `$B/arms/` без своего `pairs/` ТИХО потеряли бы калибровку нарезки,
это сдвинуло бы `segmentationSnap` ⇒ черновой снапшот ⇒ перекупка всех черновиков ×5».
**Что показало исполнение (два независимо достаточных опровержения, оба за $0):**
1. **Без каталога `pairs/` прогон ПАДАЕТ ГРОМКО, а не молча.** `prompts_root` откатывается на
несуществующий каталог, и загрузчик отказывает ещё до всякой нарезки:
```
tmctl: config .../nopairs/pipeline.yaml:
- gates.terminology is enabled but its role prompt is missing — expected .../prompts/zh-ru/terminologist.md
- gates.terminology.classify_types is on but its prompt is missing — expected .../prompts/zh-ru/classifier.md
RC=10 (отказ, $0, ни одного вызова)
```
2. **Даже с пар-слоем БЕЗ блока `segmentation` снапшот НЕ ДВИГАЕТСЯ.** Дженерик-умолчания в
`internal/config/pipeline.go:895-905` — это `1797 / 3200 / 1.1978 / 0.3852`, то есть **ровно те же
числа**, что несёт `pairs/zh-ru.yaml`. Прогон с вырезанным блоком дал снапшот
`8d5721f6559624d9…` — побайтно тот же, что и полный конфиг.
**Что остаётся верным:** механизм тихого отката у `LoadPair` реален, и для пары, чья калибровка
ОТЛИЧАЕТСЯ от дженерик-умолчаний, ловушка сработала бы. Для zh-ru — нет. Решение держать все пять
конфигов рук рядом с одним `pairs/` остаётся в силе, но его довод теперь честный: **оно исключает
расхождение двух копий пар-слоя**, а не спасает от перекупки, которой здесь не было.
## 9-ТЕР. ТРИ РЕШЕНИЯ ПРИБОРА, ОБЪЯВЛЕННЫЕ ДО ЧИСЕЛ
Найдены верификацией собственной работы (агент-контролёр + мои пере-проверки), исправлены ДО фриза.
**(а) ⛔ E5 меряет эхо по СЫРОМУ ответу попытки 0, а не по отгружаемому тексту.** Первая редакция
прибора считала CJK по отгружаемому — а санитайзер эхо ВЫРЕЗАЕТ, и эндпойнт, блокирующий любую
рекомендацию, был слеп ровно к своему предмету. Улика, снята командой на базе холодного прогона:
```
ЮНИТ флаг CJK в отгруженном | CJK в СЫРОМ ответе попытки 0
(1,0) sanitizer_stripped 0 | 10
(1,1) — 0 | 0
(2,0) — 0 | 0
```
Колонка по отгруженному остаётся ВТОРОЙ: она отвечает на другой вопрос — что увидел читатель.
Это важно вдвойне, потому что `00-provider-quirks.md:121` прямо говорит: эхо-безопасность
РЕДАКТОРСКОЙ роли на `pro`@`low` по плотному CJK **не мерена ни разу** (пять чистых хопов 31.08 —
роль ПЕРЕВОДЧИКА). ⇒ этот замер и есть первый такой контроль в проекте, и слепой E5 обнулил бы его.
**(б) ⚠ ПЕРЕКОС ПОРЯДКА ПРИ ЗАПИНЕННОМ СИДЕ — посчитан до чисел, сид НЕ трогается.** Выпавшая
перестановка на n=20 несимметрична:
```
первой клеткой юнита: p7-low 10 · p9-low 6 · p9-off 3 · p7-off 1
пара P7: low раньше off в 15 юнитах из 20 пара P9: 9 из 20
```
Первая клетка юнита не бьёт префикс-кэш и потому дороже примерно на 6000·($1.32$0.044)/1e6 ≈
**$0.0077**. ⇒ E2 у пары P7 систематически наклонён ПРОТИВ `low`. Сид — часть пре-рега и не
меняется; вместо этого E2 печатается в ЧЕТЫРЁХ заранее объявленных вариантах: попытка 0 ×
фактическая цена (основной) · итог юнита × фактическая цена · попытка 0 × строгое `delivered` ·
попытка 0 × **кэш-независимая цена** (весь вход по полной ставке — снимает перекос целиком).
На E1 это не влияет вовсе: `` от кэша не зависит.
**(в) ⚠ ДЛИНА ВХОДА ЮНИТА СОБИРАЕТСЯ ИЗ ЧАНКОВ ПО МАНИФЕСТУ.** Юнит редактуры и чанк черновика —
разные единицы: в этом стенде 20 юнитов на 35 чанков, и **у 15 юнитов из 20 `chunk_count > 1`**.
Ключ `(глава, индекс)` у них СОВПАДАЕТ, поэтому «взять первый чанк» не дало бы ни ошибки, ни
пустоты — только занижённую ковариату у трёх четвертей выборки. Прибор суммирует
`first_chunk_idx … first_chunk_idx+chunk_count1`; неполный юнит становится `None`, а не полусуммой.
Проверено прямым тестом функции (сценарий 0 контроля).
## 9-БИС. ⭐ ПРОВОДНАЯ УЛИКА ЕСТЬ — план утверждал обратное
План §3 «Дыра 2»: «У движка нет проводного артефакта — стадийный лог эффорт не печатает… Оба
свидетеля косвенные; побайтной улики у движка нет». **Это неверно, и опровержение бесплатно.**
`internal/obs/logging.go:72` `LogLLMExchange` пишет **сырое тело запроса и ответа** на уровне DEBUG,
когда включены `LOG_LLM_BODIES=1` И `LOG_LEVEL=debug`. Вызывается из `internal/llm/httpllm.go:415` —
это и есть OpenAI-совместимый путь, по которому едут и `deepseek`, и `zai`. Тело режется до 32 768 Б
**с сохранением ОБОИХ концов** — это и делает улику доступной: Go маршалит `map` с сортировкой ключей,
и `reasoning_effort` ложится ПОСЛЕ длинного `messages`, то есть в ХВОСТ тела, который обрезание сохраняет. Логируются ТОЛЬКО тела —
никогда URL и никогда заголовки, поэтому ключ утечь не может (комментарий там же, :70).
⇒ **Пред-полёт идёт с включённым каналом**, и в пре-рег ложится побайтная улика: у рук `low` в теле
стоит `"reasoning_effort":"low"`, у рук `` этого ключа НЕТ, у референса стоит
`"thinking":{"type":"disabled"}`. Это прямо исполняет требование промта §7.1 «прочитай вслух тело
запроса каждой руки». Пачка идёт уже БЕЗ канала — он нужен один раз, а объём логов не бесплатен.
⚠ Новой экспозиции текста это не создаёт: логи ложатся в `$B/logs/` внутри каталога книги, где сам
текст книги уже лежит.
**Второе бесплатное подтверждение, которое дал сам движок.** На `manifest` конфига `glm-off` он
напечатал предупреждение, а на четырёх руках креста — нет:
```
level=WARN msg="a stage sends the SOURCE text to a model whose wire SUPPRESSES thinking —
the ratified echo zone (D19.1 point 2: reasoning-off + dense CJK is a property of the model
class, not a vendor quirk)... it costs a paid call per hit" stages=edit→glm-5 source_lang=zh
```
То есть движок сам различает: у `glm-5` при `off` размышление ВЫКЛЮЧЕНО, а у `deepseek-v4-pro` при
`off` — нет (ключ просто не отправляется, thinking остаётся включённым). Ровно то различие, на
котором стоит дизайн, — предъявлено движком, а не конспектом.
## 10. ЧТО ФРИЗ НЕ ПОКРЫВАЕТ
Сигнатуру Δ`prompt_tokens` — её нельзя было задать заранее (единственное известное значение 79 снято
на английской паре и на риге). Пред-полёт её **замеряет**, ФРИЗ №2 её **пинит**, и только после этого
она становится гейтом с допуском **±2 токена** и правилом «>20% юнитов вне допуска ⇒ проба
аннулируется».
## 11. ПРИБОР ПРОВЕРЕН ИСПОЛНЕНИЕМ, А НЕ САМООТЧЁТОМ
`eval/dovodka/proverka_pribora.py` строит синтетические проектные базы той же схемы с ЗАРАНЕЕ
ИЗВЕСТНЫМ ответом и прогоняет по ним настоящий `chetyre.py`. Прямой тест функции + девять
сценариев, всё зелёное:
```
0. сборка длины входа юнита из чанков → 350 / 7 / 24 / None (наивная дала бы 100 — МОЛЧА)
1. эффект ×2 заложен → прибор нашёл, Ходжес-Леман ×0.512 против истины ×0.500 (код 0)
2. эффекта нет → «НИЖЕ РАЗРЕШЕНИЯ ЗАМЕРА», не «эффекта нет» (код 0)
3. рука вся пустая → ⛔ ПРИБОР ОТКАЗЫВАЕТ (код 2)
4. ось схлопнулась → ⛔ ПРИБОР ОТКАЗЫВАЕТ, назвав prompt_sha256 (код 2)
5. треть ∅ цензурирована → посчитал и объявил величину нижней границей (код 0)
6. 19 строк-повторов на клетку → взял НАСТОЯЩИЙ вызов, ×0.462 против истины ×0.500 (код 0)
7. сигнатура сломана у 40% юнитов → ⛔ ПРОБА АННУЛИРОВАНА (код 2)
8. эхо ЗА САНИТАЙЗЕРОМ → E5 видит CJK@сырой=4 при CJK@отгруженном=0 (код 0)
9. баз нет вовсе → ⛔ ПРИБОР ОТКАЗЫВАЕТ, а не печатает нули (код 2)
```
⚠ Сценарии 3, 4, 7 и 9 ждут от прибора ОТКАЗА — это не подгонка под зелень, а проверка того, что
прибор умеет говорить «нет».
⚠ Сценарий 6 проверяет защиту, которой в первой редакции прибора НЕ БЫЛО, и она куплена чтением
данных, а не догадкой: интерливинг гоняет каждую руку 20 раз с `--max-units 1`, и каждый прогон
дописывает в `request_log` строку-ПОВТОР уже оплаченной клетки — `tm_hit=1`, все токены и цена
НУЛЕВЫЕ, но `request_hash` тот же, то есть она соединяется с тем же чекпойнтом и несёт `attempt=0`.
Проверено на базе холодного прогона: 29 таких строк из 68, у всех нули. Прибор выбирает настоящий
вызов явно (`tm_hit=0`), а не по порядку `id`.
Команда: `./eval/.venv/bin/python eval/dovodka/proverka_pribora.py`
**Цикл интерливинга `eval/dovodka/pachka.sh` проверен тем же способом** — на ПОДДЕЛЬНОМ `tmctl`,
который печатает то же, что настоящий, и продвигает БД руки. Проверялись не успешные ветки (они
очевидны), а **отказные**:
| подделка | что обязан сделать цикл | получено |
|---|---|---|
| рука `p9-low` не продвигается (оборванный вызов без чекпойнта) | заметить рассинхрон и встать | ⛔ «РУКА p9-low РАЗЪЕХАЛАСЬ: 0 вместо 1», **код 5** |
| движок вернул код 4 | стоп и вопрос владельцу, добор запрещён | ⛔ код **4** |
| движок вернул код 3 в РУКЕ (банк уже предъявлен базой — так быть не должно) | стоп | ⛔ код **3** |
| копия несёт НЕ базовый расход (сделана не из того бэкапа) | не начинать: агрегат соврал бы | ⛔ «КОПИЯ p9-low НЕСЁТ $0.61 ВМЕСТО БАЗОВЫХ $0.45», код **1** |
| рука купила ДРУГОЙ юнит при том же ЧИСЛЕ юнитов | заметить по НАБОРУ КЛЮЧЕЙ | ⛔ «эта рука `51:0` · эталон `1:0`», код **5** |
⚠ Инвариант синхронности рук существует потому, что блокировка трудности держится на «k-я клетка —
ОДИН И ТОТ ЖЕ юнит у всех пяти рук». Оборванный вызов сдвинул бы одну руку на юнит назад, и дальше
руки сравнивали бы РАЗНЫЕ чанки — **молча, без единой ошибки**. Проверка после каждой клетки:
сверяются НАБОРЫ КЛЮЧЕЙ `chapter:chunk_idx` всех пяти рук, а не только их число — счётчик совпал бы
и у рук, купивших разные юниты. База для агрегата берётся из ФАЙЛА БЭКАПА, из которого сделаны копии,
а не с живой базовой БД: иначе любое касание базы сдвинуло бы вычитаемое молча.
## 12. ЧЕГО В ЗАМЕРЕ НЕТ НАМЕРЕННО
Судейства качества (качество при сниженном усилии — вне скоупа; только $0-гейты и ось безопасности
E5) · калибровки ставки платформы · правок `models.yaml`, capabilities, Go, боевого `pipeline-c1.yaml` ·
смены модели, черновика, дифф-контракта · флага `--resnapshot` — **никогда**.