Freeze the second door-to-file run before any money: what it buys, the measure it will answer, the limits it declares and the instruments that take them

This commit is contained in:
heaven 2026-09-11 22:43:23 +03:00
parent 2792058552
commit 4ee7f03a79
4 changed files with 869 additions and 0 deletions

View file

@ -0,0 +1,208 @@
# Эксперимент 25. Холодный прогон B — «от двери до ФАЙЛА» во ВТОРОЙ раз, и впервые с вычиткой
> **Пре-регистрация.** Всё, что ниже строки «ГРАНИЦА ФРИЗА», написано ДО первого платного вызова и
> закоммичено фриз-коммитом. Результаты дописываются ПОСЛЕ прогона отдельной секцией и ничего в
> пре-реге не правят: расхождение НАЗЫВАЕТСЯ, а не подгоняется.
**Зона:** сквозной прогон · **Пак:** `docs/COLD_RUN_B_SESSION_PROMPT.md` (редакция `3fce077`) · **Дата:** 11.09.2026
**Предмет:** четыре вещи, которых нет ни у кого, плюс заказ владельца на ВЫЧИТКУ боевого текста.
## 0. Что именно покупается, и чего этот прогон НЕ покупает
**Покупается:** (1) первое исполнение двух миграций схемы НА ПЛАТНОМ ПРОГОНЕ · (2) числа
консистентности С ПРОГОНА, а не пере-сканом сохранённой базы A · (3) платформенный пак СКВОЗЬ ·
(4) пре-регистрированный ЗНАМЕНАТЕЛЬ денежной дыры и проверка её ЗНАКА · (5) ⭐ вычитка ВСЕГО
перевода против ВСЕГО оригинала отдельным опус-читателем и сверка ПРИБОРОВ движка с тем, что он увидел.
**НЕ покупается и не оплачивается ни центом:** ответ на «безопасно ли редактору думать меньше»
(ряд 433) — это парный дизайн, отдельные деньги, отдельный заказ. B идёт на боевых настройках как есть.
**НЕ покупается ВЕЛИЧИНА доли выброшенного.** B — ЕЩЁ ОДИН одиночный прогон. Квирки §3д: при sd
логарифма 0.82 парный дизайн ловит эффект ×2.5 за **13 пар**, ×2.0 за 22, ×1.4 за 93. **n = 2 этой
нормы не выполняет и выполнить не может.** Писать «25.4 % подтверждено» по двум розыгрышам — ошибка.
## 1. ГРАНИЦА ФРИЗА — что зафиксировано до первого цента
| Предмет | Значение |
|---|---|
| Фриз-sha (КОД, которым собраны бинари) | `baa06cef7b2b30b8f7cbe031f3d0fabc03a01a55` |
| Фриз-дерево | локальный **клон** `~/tm-coldrun-b/freeze` (0 грязных строк; контроль — главное дерево в тот же момент 1) |
| Бинари | `tmctl` · `tmplatformd` · `tmplatformctl` · `stub` · `zeropipe`**все пять** из клона, у каждого `vcs.revision=baa06ce…`, `vcs.modified=false`, строк `vcs.*` = 3. Негативный контроль: та же сборка из ГЛАВНОГО дерева даёт `vcs.modified=true`. ⚠ Прогон A объявил девиацией, что два его инструмента собраны НЕ из фриза (§12.5) — здесь эта девиация закрыта построением |
| Исходник (оригинал) | `books/gu-zhenren/coldrun-v16/guzhenren-ch1-10.gb18030.txt`, 57838 байт, sha256 `0b5f9b0266d8c32ac717a41211d2ed15d4f2a07f37a701fb686e255ab6c89f37` |
| Исходник (СРЕЗ, что покупается) | главы 13, **17564 байта**, sha256 `ed870ba6065ce3eb4eec12e80238efbc2be8511f38334ab1d92d5693f4ef4df9`. Нарезан МНОЙ из оригинала (`head -c`), байт-в-байт равен срезу прогона A (`cmp`). **Граница чистая:** декодируется целиком в **9021 знак**, кончается на границе абзаца, несёт **ровно 3** заголовка `第…节` при **10** в целом файле (контроль напечатан рядом) |
| Фактический pipeline-YAML | `~/tm-coldrun-b/mirror/cfg/pipeline-c1.yaml`, sha256 **`a46b33ee65b7713b4fbf86f64c788ba6b11eb0a219170c145a6205088156ad49`**, байт-в-байт равен `backend/configs/pipeline-c1.yaml` фриза (`cmp`) |
| Реестр моделей | `~/tm-coldrun-b/mirror/cfg/models.yaml`, sha256 `835af7b2d8830e6058b650ab742603d228febbd663978d88e0fe496ff78b5c1c` |
| Артефакт контраста | `mining-contrast.zh.txt` = jieba 0.42.1 `dict.txt`, sha256 `7197c3211ddd98962b036cdf40324d1ea2bfaa12bd028e68faa70111a88e12a8`, 349 046 строк. ⚠ `CheckMiningContrast` делает ТОЛЬКО `os.Stat` и удовлетворился бы фальшивым кандидатом — sha сверена руками |
| Шаблон книги | `~/tm-coldrun-b/book-template-c1.yaml`, sha256 `4118a43059dba64a45290db73252a1306af341ff4c429100314256c95ca2f24d` |
| Потолки книги (ДВИЖКОВЫЕ, в шаблоне) | `book_usd: 1.25` · `day_usd: 2.50` — те же, что у A, ради сравнимости |
| Потолок ПЛАТФОРМЫ | холд ≈$2.24 (формула ниже), грант учётки **$2.50** |
| Стоп-правило СЕССИИ | факт **$1.00** — раньше обоих потолков; достигнут ⇒ СТОП и пинг, не решение сессии |
### 1.1 ⭐ ДВЕ КАРТЫ СРАВНИМОСТИ A↔B, которых у прогона A не было
1. **ПОКУПАЮЩИЙ файл тождествен.** sha `a46b33ee…` — та же, что в пре-реге прогона A. Что именно
покупается, между прогонами не изменилось ни на байт.
2. **ЦЕНОВАЯ МОДЕЛЬ тождественна.** `models.yaml` фриза A и фриза B различаются **ровно на 10 строк,
и все десять — КОММЕНТАРИИ** (не-комментарных изменённых строк: **0**). ⇒ долларовые числа A и B
считаны одним прайсом и сравнимы напрямую.
3. ⛔ **А КОД — НЕТ, и это записывается ОГРАНИЧЕНИЕМ, а не замалчивается.** Между `b0f5d89` (фриз A) и
`baa06ce`: **118 коммитов**, `backend/` **52 файла** (+7678/85), `platform/` **30 файлов**
(+3945/158), включая денежный путь. **B меряет ДРУГОЙ движок.** Прямой строкой:
**двойная оплата могла измениться из-за НАШИХ правок, а не из-за провайдера.**
### 1.2 Срез по $0-манифесту (`tmctl manifest --json`, бинарь фриза B)
| Глава | `units_total` | `chunks_total` | `expected_usd` | знаков |
|---|---|---|---|---|
| 1 `第一节:纵身亡魔心仍不悔` | 2 | 2 | 0.032424 | 3287 |
| 2 `第二节:逆光阴五百年觉悟` | 1 | 2 | 0.028459 | 3051 |
| 3 `第三节:请一边玩蛋去` | 1 | 2 | 0.023237 | 2421 |
| **книга** | **4** | **6** | `expected_usd 2.08412008` · `book_once_usd 2.00` · `step_max_usd 0.13647876` | 8759 |
Смета ТЕКСТА — сумма глав, **$0.084120**; `book_once_usd` $2.00 это ПОТОЛКИ контура банка
(`gates.terminology.budget_usd 1.00` + `classify_budget_usd 1.00`), а не смета.
**Все семь чисел этой таблицы совпали с манифестом прогона A до последнего знака** — при другом
бинаре, другом каталоге и другом `book_id`. Это сильная улика тождества покупаемого.
## 2. ⛔ МЕРА ДЕНЕЖНОЙ ДЫРЫ — пре-регистрируется ЗДЕСЬ, до первого цента
**Прибор ПРОВЕРЕН ДО ПРИМЕНЕНИЯ:** те же запросы, прогнанные по сохранённой базе прогона A, дают
его числа. Это не «похоже», а пере-снято: 33 строки · 33 уникальных `id` · 2 трейса · $0.419423.
* **единица** = `(chapter, chunk_idx, stage, role)`. Номера попытки в ключе НЕТ намеренно: единица
ОХВАТЫВАЕТ попытки. ⚠ У стадии `terminology` все строки несут `chapter = 0`, то есть `chunk_idx`
там — индекс майнинг-батча, а не чанк главы. Одно слово покрывает ДВА предмета; в каждом числе
называется, какой именно.
* **знаменатель** = единицы, за которые заплачено хоть раз (`sum(cost_usd) > 0`). **Печатаются ОБА
счёта.** На A фильтр не отсеял ничего (17 и 17) ⇒ его поведение не проверялось ни разу.
* **ЧИСЛИТЕЛЬ ПЕРВЫЙ — выброшенная покупка:** единицы, где есть хоть одна оплаченная и НЕ принятая
попытка (`ok = 0 AND cost_usd > 0`). **База A: 4 из 17 = 23.5 % ЕДИНИЦ**, тогда как по ВЫЗОВАМ то
же событие даёт 4 из 27 = **14.8 %**. Две меры расходятся в полтора раза — потому мера и объявлена.
* **ЧИСЛИТЕЛЬ ВТОРОЙ — ПЕРЕ-МАЙНИНГ БАНКА, и он НЕ называется «пере-оплатой».** Единицы, за
ПРИНЯТЫЙ результат которых заплачено больше одного раза (`ok = 1`). На A это вся стадия
терминологии. **Главное число — «все платежи КРОМЕ ПЕРВОГО» = $0.028742 (6.9 %), нижняя граница;
рядом печатается «все КРОМЕ ПОСЛЕДНЕГО» = $0.035723 (8.5 %).** Разница 24 %, и обе ориентации
получены ПОРЯДКОМ — тем самым методом, на котором прошлая смена получила ложные 54.9 %. Поля,
называющего вызов-источник, у строк банка НЕТ (`glossary` 21 колонка, вызова среди них нет;
`glossary_revisions` 0 строк), поэтому ориентация выбирается явно и объявляется.
**И почему это НЕ «деньги за пустоту»** (замерено мной на артефактах A ДО прогона B): второй
заход терминологии ИЗМЕНИЛ передачу у **12 термов из 69**, и **11 из 12 уехали читателю именно в
ФИНАЛЬНОЙ форме** (стоп-форма — 0 из 12; один терм не встретился ни в какой). Это ЦЕНА
ПЕРЕ-МАЙНИНГА БАНКА ПОСЛЕ ЧЕЛОВЕЧЕСКОЙ ПОДПИСИ, а не выброшенная покупка.
* **ДВА ПУТИ К ДЕНЕЖНОМУ ЧИСЛУ.** Путь 1 — по колонке `degraded`; путь 2 — по `ok = 0 AND cost_usd > 0`,
колонки `degraded` не касаясь. На A оба дали **$0.106472 = 25.4 %**, но совпадают они ПО
ПОСТРОЕНИЮ. ⇒ совпадение на B — слабая улика; **РАСХОЖДЕНИЕ — сильная находка**: классификатор и
признак приёмки разъехались. ⚠ **25.4 % — доля ДЕНЕГ, 23.5 % — доля ЕДИНИЦ**, подменять нельзя.
## 3. ⛔ ДВЕ СЛЕПЫЕ ПОВЕРХНОСТИ ПРИБОРА КОНСИСТЕНТНОСТИ, НАЗВАННЫЕ ДО ПРОГОНА
Обе найдены на артефактах прогона A, обе меняют то, как прибор B строится.
**(а) ГРАНИЦА БАНКА.** Прибор A сверял отгруженный текст со СТОП-ПРЕДЛОЖЕНИЕМ банка
(`project.db.bank.json`, снят в момент подписи), а читатель получил текст, сделанный под ФИНАЛЬНЫМ
банком: терминология пере-майнится ПОСЛЕ подписи, и редакторская волна идёт после неё
(по `request_log` прогона A: терминология-2 кончается 01:42:10, редактор идёт 01:43:54 → 01:54:44).
**прибор B читает ФИНАЛЬНЫЙ банк и ПЕЧАТАЕТ, какую версию он прочёл.** Класс — `D39.228` п.5.
**(б) ЗНАМЕНАТЕЛЬ САМОГО ПРИБОРА.** На A он нашёл передачу у **40 термов из 69**, а у **29** вернул
пусто — и записал их в «консистентные». Его итог «отдано более чем одной формой: 0» посчитан по 40,
а читается как утверждение о книге. ⇒ **прибор B печатает всегда: термов всего N · передачу нашёл у
M · пусто у K, и K проверяется ТЕКСТОМ**, а не принимается за консистентность.
## 4. Пре-рег-таблица: узел → что ДОЛЖНО произойти → чем предъявлено → что считается провалом
| # | Узел | Ожидание (числом или строкой) | Чем предъявлено | Провал |
|---|---|---|---|---|
| 1 | Стенд — мой | слушатель `127.0.0.1:8098` принадлежит МОЕМУ pid; на `11434` в платной фазе НЕ слушает НИКТО | `ss -ltnp` + pid-файл | чужой процесс на порту ⇒ СТОП |
| 2 | Код — из фриза | у ПЯТИ бинарей `vcs.revision=baa06ce…`, `modified=false`, строк `vcs.*` > 0 | `go version -m` | любое из трёх не так ⇒ СТОП |
| 3 | Гость и деньги | ОТДЕЛЬНАЯ платная учётка; грант $2.50 одной строкой `credit_ledger.kind='grant'` | `tmplatformctl balance` + строка леджера, ФИЛЬТРОВАННАЯ по учётке | дымовые деньги попали в счёт ⇒ число недействительно |
| 4 | Интейк | `201`, `chapter_count = 3`; ⚠ `character_count` ≈ 9677 (счёт интейка по потоку, ПРИБЛИЗИТЕЛЬНЫЙ), точный — `source_chars = 8759` | тело 201 + строка `books` | не 3 главы ⇒ СТОП до оплаты |
| 5 | Срез совпал | `units_total = 4`, по главам **2/1/1**, `chunks_total = 6` | `chapters`+`units` в Postgres против §1.2 | расхождение ⇒ СТОП до оплаты |
| 6 | Цена и бонд | `term_consistency_funded: true`, `verdict: covers_all`, `affordable_chapters: 3`, холд **2 241 631 µUSD** = ⌈84 121 × 1.25⌉ + 136 479 + 2 000 000 | `GET /v0/books/{id}/run-options`, тело в файле | `false` ⇒ СТОП до оплаты |
| 7 | Старт | `202`, `status: translating`, `ordered_chapters: 3`, `runs.bond_funded = t`. ⚠ **Ожидается, что ответ старта скажет `term_consistency_funded: false`** при уже выставленном `bond_funded = t` — находка 7 прогона A; ВОСПРОИЗВЕДЕНА мной на $0-руке | ответ + строка `runs` | отказ старта ⇒ СТОП и пинг |
| 8 | Ключи живы | первый платный вызов не отказан по авторизации | `request_log.err` на первой строке | отказ провайдера ⇒ СТОП и пинг (не наша поломка) |
| 9 | Кадры | `events.jsonl` несёт `hello · progress · unit_done · spend · bank_stop · finished`; `run_attempts.last_seq` растёт **ПО ПОПЫТКАМ** | счёт кадров по типам, per-attempt | кадров нет при живом юните ⇒ дефект |
| 10 | Банк-стоп | `runs.status = awaiting_bank`, exit **3**, `.bank.json` несёт непустой `proposed[]` и `terms: []` | статус + exit + КОПИЯ сайдкара (резюм её уничтожает) | стоп не наступил ⇒ исход «узел не наступил», не провал |
| 11 | Проекция банка | `bank_terms` = **0** ПОКА прогон не закрыт и **> 0** ПОСЛЕ | `GET …/bank` дважды + `select count(*)` | ноль ПОСЛЕ закрытия ⇒ дефект |
| 12 | Дверь правок | `preview:true``changed:true`, на диске НИЧЕГО; `preview:false``state: applied` + `mined-delta.yaml` | тела обоих ответов В ФАЙЛАХ + sha файла до/после | преview написал на диск ⇒ дефект |
| 13 | Резюм (**РОВНО ОДИН**) | `202`, вторая строка `run_attempts`, `exit_code = 0` | `run_attempts` | не возобновился ⇒ дефект |
| 14 | Книга переведена | `units.state = translated` у всех **4** юнитов | `select state, count(*)` + контрольный счёт юнитов книги | любой не `translated` ⇒ книга не доведена |
| 15 | Сборка | `complete: true`, `pending/withheld/incomplete/stale/ghost = 0`, `stale_unknown: false`, `config_drift: false` | stdout `tmctl build` (на API этих полей НЕТ) | любое не так ⇒ книга не целая |
| 16 | Выгрузка | `exports.state = ready`, `exports.complete = t`, `size_bytes` = числу скачанных байт, ОБА формата | строка `exports` + длина скачанного | `complete` не `t` ⇒ дверь отдала книгу с дырой |
| 17 | ФАЙЛ | байты скачаны, sha256 напечатан; EPUB читается структурно (zip + `container.xml` + `nav` + spine = 3) | sha256 + разбор zip | не открывается ⇒ провал |
| 18 | Человек прочитал | первый абзац — связный русский текст | цитата в отчёте | не текст ⇒ провал |
| 19 | Деньги сошлись | Σ `request_log.cost_usd` ≈ кадр `spend` ≈ Σ `settlement` ЭТОЙ учётки ≈ (грант баланс); открытых резерваций **0** | четыре счёта в µUSD рядом, ВСЕ фильтрованы по учётке | расхождение — НАЗВАТЬ, не подгонять |
| 20 | Ничего не реплеено | `tm_hit = 1` печатается С РАЗБИВКОЙ ПО ПОПЫТКАМ **по `trace_id`** (он несёт номер попытки), `distinct model_actual` не содержит `local-*` | запросы к `project.db` | есть `local-*` ⇒ прогон измерил заглушку |
| 21 | **v17 `wave_selection`** | **10 строк = 6 `draft` + 4 `edit`**, и волны стоят над РАЗНЫМИ снапшотами (черновик — базовый банк, редактор — обогащённый) | `select wave, count(*)` + `snapshot_id` + `injected_srcs` | строк нет ⇒ **пинг, а не ноль в отчёте**: писатель гейтится `r.memory != nil` |
| 22 | **v18 `reasoning_in_completion`** | **ТРЁХЗНАЧНО**: сколько `NULL`, сколько `0`, сколько `>0`, сумма = числу строк. Ожидание: на DeepSeek `>0` преобладает | три счёта + контроль суммы | «не ноль» вместо трёхзначности ⇒ вопрос не задан |
| 23 | Консистентность | `I1`/`I2` С ПЕЧАТАЕМЫМИ знаменателями + «термов N · передачу нашёл у M · пусто у K» + **какую версию банка прочёл прибор** | `QualityReport().Consistency` + свой счёт по отгруженному тексту | число без знаменателя в этом паке не принимается |
| 24 | ⭐ Вычитка | опус-читатель получает ВЕСЬ перевод и ВЕСЬ оригинал; печатает знаков оригинала (**контроль: 9021**), знаков перевода, ПОСЛЕДНЕЕ предложение обоих | его отчёт | не напечатал доказательство дочитывания ⇒ отчёт не принимается |
| 25 | ⭐ Сверка приборов | таблица 2×2 заполнена ИМЕНАМИ находок; левая нижняя клетка («прибор смолчал, читатель нашёл») названа поимённо | §4.6 отчёта | клетка пуста без довода ⇒ сверка не сделана |
## 5. Стоп-правила (остановка и пинг, не решение сессии)
1. фактическая трата дошла до **$1.00** (1 000 000 µUSD);
2. `runs.paused_reason` НЕПУСТО — любое из четырёх (`run_limit_reached · credit_exhausted · daily_ceiling · ceiling_unknown`);
3. срез не совпал с §1.2 — стоп ДО оплаты;
4. `term_consistency_funded: false` в `run-options` — стоп ДО оплаты;
5. бинарь не из фриза — стоп до устранения;
6. что-либо слушает `127.0.0.1:11434` в платной фазе — стоп: прогон измерял бы заглушку.
## 6. ⛔ ОГРАНИЧЕНИЯ, ОБЪЯВЛЕННЫЕ ДО ПРОГОНА
1. **ЭСКАЛАЦИЯ В B НЕ ИСПОЛНЯЕТСЯ.** `pipeline-c1.yaml:223` несёт `escalation.budget_usd: 0`, а
комментарий строкой `:221` гласит: «Приёмочная сессия 蛊真人 ОБЯЗАНА выставить budget_usd>0».
Решение оркестратора: **не трогать**, потому что тождество покупающего файла и ценовой модели
(§1.1) стоит дороже одного непроверенного пути. ⇒ B, как и A, о работе эскалации не говорит
НИЧЕГО. Сам этот факт идёт находкой вне заказа: механизм построен, ратифицирован и не может
исполниться ни на одном боевом конфиге без ручного оверрайда, которого никто ни разу не сделал.
2. **Судьи на пути нет** (`role: judge` живёт только в `c2`), **гейт `coverage` выключен** ⇒ из пяти
гейтов работают четыре. Проверяемый путь: черновик → редактор → четыре $0-гейта → выгрузка.
3. **`epubcheck` не гоняется** — `java` на хосте нет и поставить нельзя. EPUB судится структурно.
4. **ТРИ следа — не три свидетеля.** Postgres платформы МАТЕРИАЛИЗУЕТСЯ из `events.jsonl` движка, а
текст и файл платформа берёт теми же вербами `tmctl`. По-настоящему независимы только скачанные
байты и их sha256, плюс systemd-маркеры выхода. Биллинга провайдера у нас нет вовсе.
5. **Доллары — НАША модель поверх токенов провайдера.** Провайдер сообщает ТОКЕНЫ; цену умножаем мы,
по пину `models.yaml`. На 11.09 пин flash был ВЫШЕ вендорской страницы на 13 % (D39.247 п.3).
6. **Пик/офф-пик.** Прогон A шёл 01:2901:54 UTC в пятницу — ВНУТРИ пикового окна DeepSeek
(0104 и 0610 UTC, пн–пт). B идёт вечером пятницы, то есть в ОФФ-ПИК. Леджер осознанно считает
по ПИКОВОЙ цене (`D39.136`: потолок обязан срабатывать раньше) ⇒ **записанная цена B будет вдвое
выше настоящего счёта, а записанная цена A — нет.** На сравнение ЛЕДЖЕРОВ это не влияет (оба
пиковые), на «сколько мы заплатили» — влияет вдвое. Называется здесь, чтобы не открылось потом.
7. **ПРЕТРЕЙН — процедура, а не запрет.** `蛊真人` узнаётся моделями 4 из 5. **Каждая находка о
качестве обязана нести ДВЕ цитаты рядом — фрагмент ОРИГИНАЛА и фрагмент ПЕРЕВОДА.** Находка,
которая не может показать место в исходнике, — не находка, а воспоминание.
## 7. Инструменты (замораживаются вместе с этим пре-регом)
* `eval/cold_run_b/door.py` — драйвер: гейт · вход · грант · интейк · опции · старт · вахта · улика ·
подпись · резюм · выгрузка · произвольная проба. **Каждый запрос И каждый ответ ложатся в
`evidence/http/` ДО того, как вернутся вызывающему** — дефект харнесса A, из-за которого пять
строк его пре-рег-таблицы остались без носителя (§10.12 его отчёта).
* `eval/cold_run_b/measure.py` — прибор денег и единиц §2. **Пере-снимает числа прогона A на его же
базе** как самопроверку перед применением к B.
* Из фриза A переиспользуются без правки: `eval/door_to_file/stub` ($0-заглушка провайдера),
`eval/door_to_file/zeropipe` (рендер $0-пайплайна). Оба собраны из клона B и несут его штамп.
## 8. Что уже исполнено ДО фриза и чем предъявлено ($0)
* **`GET /v0/capabilities``export_formats: ["txt","epub"]`** (непусто ⇒ все три гейта выгрузки
выполнены), `contract_version 0.15.0`, `bank_corrections_enabled: true`.
* **Пин фазы 0 — ЗАГРУЗЧИК, а не скан.** Движок ЗАГРУЗИЛ рендер $0-пайплайна бесплатным вербом
(`tmctl manifest --json`, EXIT=0). Контрольная величина рядом: достижимых ПЛАТНЫХ моделей в
`pipeline-c1.yaml`**8**, в рендере — **0**, счёт по РАЗОБРАННОМУ YAML, а не по тексту.
* **$0-ДЫМ ПРОЙДЕН ЦЕЛИКОМ на ОТДЕЛЬНОЙ книге** (синтетический zh-текст, 1206 байт, 4 главы,
sha256 `10637234312895da4665ed0dff84f25dfacdffd842871425b1bba1cc612b80da`) и на ОТДЕЛЬНОЙ учётке:
стенд · гость · грант · интейк 201 · опции (4 главы, 4 юнита, бонд профинансирован) · старт 202 ·
кадры · банк-стоп (exit 3) · дверь правок (preview НИЧЕГО не пишет — проверено sha файла до и
после) · резюм (exit 0) · выгрузка ОБОИХ форматов, `complete = t`, файлы скачаны.
* ⭐ **ОБЕ МИГРАЦИИ УЖЕ ИСПОЛНИЛИСЬ ЖИВЬЁМ, и это честно называется здесь, а не приписывается B.**
На дымовой базе: `wave_selection`**8 строк = 4 `draft` + 4 `edit`**, волны стоят над РАЗНЫМИ
снапшотами (`6dabf7e5…` без инъекции против `76670592…` с тремя термами) ⇒ **ось, которой у
`retrieval_state` нет, работает.** `reasoning_in_completion``NULL` на всех 16 строках, и это
ВЕРНЫЙ трёхзначный ответ: локальная заглушка поля не сообщает. ⇒ **B покупает не «исполнилась ли
миграция», а её поведение НА ПРОВАЙДЕРЕ, КОТОРЫЙ ЭТО ПОЛЕ ШЛЁТ.**
* ⭐ **МИГРАЦИЯ ПРОВЕРЕНА НА ЗАПОЛНЕННОЙ БАЗЕ, а не только на пустой.** Копия базы прогона A
(v16, 33 строки `request_log`, 69 строк `glossary`) открыта бинарём B: `v16 -> v18`, EXIT=0, строки
целы (33 и 69), `wave_selection` создана пустой, **`reasoning_in_completion` = `NULL` на всех 33
ранее написанных строках** — ровно то, что миграция обещает. Оригинал не тронут (sha сверена
до и после). Пустая база про данные не говорит ничего; эта — говорит.

26
eval/cold_run_b/README.md Normal file
View file

@ -0,0 +1,26 @@
# `cold_run_b` — харнесс холодного прогона B
Инструменты пака «ХОЛОДНЫЙ ПРОГОН B» (`docs/COLD_RUN_B_SESSION_PROMPT.md`). Пре-регистрация,
таблица ожидаемых исходов и результаты — `docs/experiments/25-door-to-file-b.md`.
| Файл | Что делает | Чем проверен |
|---|---|---|
| `door.py` | драйвер: гейт · вход · грант · интейк · опции · старт · вахта · улика · подпись · резюм · выгрузка · произвольная проба с ЗАРАНЕЕ записанным ожиданием. **Отказывает**, а не предупреждает | $0-дым прошёл им целиком: интейк → банк-стоп → подпись → резюм → обе выгрузки → файл |
| `measure.py` | прибор денег и ЕДИНИЦ: знаменатель, оба числителя, обе ориентации второго, два пути к денежному числу | `--selfcheck` пере-снимает КАЖДОЕ опубликованное число прогона A на его же базе и отказывает, если хоть одно сдвинулось |
## Три вещи, ради которых это не копия харнесса прогона A
1. **Каждый запрос и каждый ответ ложатся в файл ДО того, как вернутся вызывающему.** Драйвер A
печатал тела на экран и не писал их никуда — пять строк его пре-рег-таблицы остались без
носителя, и целая находка состояла только из тела ответа, которого не сохранилось.
2. **Живость читается ПО ПОПЫТКАМ.** `max(last_seq)` по всем попыткам во время второй выглядит
замершим на значении первой. Номер попытки не вычисляется по времени — он НАЗВАН в `trace_id`.
3. **Деньги читаются фильтром по ОДНОЙ учётке.** Сборщик A суммировал леджер по всем учёткам стенда
и подал два гранта как один; дымовые деньги и боевые не должны встречаться ни в одном числе.
## Что переиспользуется из фриза прогона A без единой правки
`eval/door_to_file/stub` ($0-заглушка провайдера `kind: local` на `127.0.0.1:11434`) и
`eval/door_to_file/zeropipe` (рендер боевого пайплайна на $0-пару, печатает счёт достижимых ПЛАТНЫХ
моделей в обоих файлах). Оба собраны из клона B и несут его VCS-штамп — девиация §12.5 отчёта A
(«два моих инструмента собраны не из фриза») здесь закрыта построением.

508
eval/cold_run_b/door.py Executable file
View file

@ -0,0 +1,508 @@
#!/usr/bin/env python3
"""door.py — the driver of cold run B: one book from the intake door to a downloaded file.
Why this exists next to `eval/door_to_file/drive.sh` rather than as an edit of it: that harness is
frozen evidence of run A, and run A named two defects in it that this run cannot inherit.
* IT PRINTED EVERY HTTP ANSWER TO THE SCREEN AND WROTE NONE TO DISK. Five of A's pre-registration
rows ended with no carrier at all (§10.12 of its report): the `run-options` body, the 202 of the
start, the corrections door's `changed`/`applied`, and a whole finding that consisted of nothing
but a response body. Here EVERY request and EVERY answer is appended to evidence/http/ before
the caller sees it, so an assertion about a body can be re-read rather than remembered.
* ITS LIVENESS WATCH READ max(last_seq) ACROSS ATTEMPTS, so during the second attempt the number
looked frozen at the first attempt's value while work was going on. Here every liveness number
is read PER ATTEMPT, and the attempt is taken from the row, never inferred from time.
Two rules this file keeps because breaking them is what makes a run measure nothing:
* money is read filtered by ONE account. A's collector summed the ledger over every account on the
stand and reported two grants as one (§14.2); the smoke's account and the paid account must never
meet in a number.
* a claim about a count prints its control quantity beside it. "0 rows" and "no such table" look
the same in output, and in money that is the difference between "did not spend" and "did not look".
"""
import argparse, http.cookiejar, json, mimetypes, os, re, subprocess, sys, time, urllib.error, urllib.request, uuid
W = os.environ.get("W", "/home/ubuntu-26/tm-coldrun-b")
ADDR = os.environ.get("ADDR", "127.0.0.1:8098")
DSN = os.environ.get("TM_PLATFORM_DSN", "postgres://postgres@/tm_coldrun_b?host=/tmp&port=55433&sslmode=disable")
PSQL = os.path.expanduser("~/.local/pgsql/bin/psql")
HTTPDIR = os.path.join(W, "evidence", "http")
STATE = os.path.join(W, "stand", "run-state.json")
# An opener that does NOT go through the environment's proxy: this host exports a webshare proxy and
# a WinINET-style NO_PROXY that Go, curl and urllib all fail to parse, so a request to 127.0.0.1 goes
# out to the proxy and comes back 403 (provider-quirks, "Прокси на localhost").
# The jar is a FILE: each step is its own process, and a jar that lives only in memory logs in again
# on every call — which looks like a working session right up to the moment a step needs the one it
# thought it had.
JARFILE = os.environ.get("TMB_JAR", os.path.join(W, "stand", "cookies.txt"))
JAR = http.cookiejar.MozillaCookieJar(JARFILE)
try:
JAR.load(ignore_discard=True, ignore_expires=True)
except Exception:
pass
OPENER = urllib.request.build_opener(
urllib.request.ProxyHandler({}), urllib.request.HTTPCookieProcessor(JAR))
def jar_save():
os.makedirs(os.path.dirname(JARFILE), exist_ok=True)
JAR.save(ignore_discard=True, ignore_expires=True)
_seq = [0]
def state_load():
try:
with open(STATE) as f:
return json.load(f)
except FileNotFoundError:
return {}
def state_put(**kw):
s = state_load()
s.update(kw)
os.makedirs(os.path.dirname(STATE), exist_ok=True)
with open(STATE, "w") as f:
json.dump(s, f, indent=2)
return s
def sql(q):
out = subprocess.run([PSQL, DSN, "-At", "-F", "\t", "-c", q],
capture_output=True, text=True)
if out.returncode != 0:
raise SystemExit("psql refused: " + out.stderr.strip())
return [l.split("\t") for l in out.stdout.splitlines()]
def sql1(q, default=None):
rows = sql(q)
return rows[0][0] if rows and rows[0] and rows[0][0] != "" else default
def api(method, path, body=None, ctype=None, label=None, raw=False, headers=None):
"""One request. The request AND the answer are on disk before this returns."""
_seq[0] += 1
n = _seq[0]
url = f"http://{ADDR}{path}"
req = urllib.request.Request(url, data=body, method=method)
req.add_header("X-TM-Client", "coldrun-b-driver")
if ctype:
req.add_header("Content-Type", ctype)
for k, v in (headers or {}).items():
req.add_header(k, v)
t0 = time.time()
try:
r = OPENER.open(req, timeout=300)
code, data, hdrs = r.status, r.read(), dict(r.headers)
except urllib.error.HTTPError as e:
code, data, hdrs = e.code, e.read(), dict(e.headers)
except Exception as e: # connection refused, timeout, …
code, data, hdrs = 0, str(e).encode(), {}
ms = int((time.time() - t0) * 1000)
name = f"{n:03d}-{(label or path.strip('/').replace('/', '_'))[:60]}"
rec = {"n": n, "at": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "method": method, "url": url,
"status": code, "ms": ms, "headers": hdrs,
"request_ctype": ctype, "request_bytes": len(body or b"")}
os.makedirs(HTTPDIR, exist_ok=True)
if raw:
with open(os.path.join(HTTPDIR, name + ".bin"), "wb") as f:
f.write(data)
rec["body_file"] = name + ".bin"
rec["body_bytes"] = len(data)
else:
try:
rec["body"] = json.loads(data.decode("utf-8"))
except Exception:
rec["body_text"] = data.decode("utf-8", "replace")[:20000]
with open(os.path.join(HTTPDIR, name + ".json"), "w") as f:
json.dump(rec, f, ensure_ascii=False, indent=2)
with open(os.path.join(W, "evidence", "http-index.jsonl"), "a") as f:
f.write(json.dumps({k: rec[k] for k in ("n", "at", "method", "url", "status", "ms")}) + "\n")
jar_save()
return code, (rec.get("body") if not raw else data), hdrs
def multipart(fields, filefield, filepath):
b = "----tmb" + uuid.uuid4().hex
out = []
for k, v in fields.items():
out.append(f"--{b}\r\nContent-Disposition: form-data; name=\"{k}\"\r\n\r\n{v}\r\n".encode())
fn = os.path.basename(filepath)
ct = mimetypes.guess_type(fn)[0] or "application/octet-stream"
out.append(f"--{b}\r\nContent-Disposition: form-data; name=\"{filefield}\"; filename=\"{fn}\"\r\n"
f"Content-Type: {ct}\r\n\r\n".encode())
out.append(open(filepath, "rb").read())
out.append(f"\r\n--{b}--\r\n".encode())
return b"".join(out), f"multipart/form-data; boundary={b}"
# ---------------------------------------------------------------- the gate
FREEZE_SHA = os.environ.get("FREEZE_SHA", "baa06cef7b2b30b8f7cbe031f3d0fabc03a01a55")
BINARIES = ["tmctl", "tmplatformd", "tmplatformctl", "stub", "zeropipe"]
def stamp(path):
out = subprocess.run(["go", "version", "-m", path], capture_output=True, text=True).stdout
vcs = [l for l in out.splitlines() if "vcs." in l]
rev = next((l.split("vcs.revision=")[1].strip() for l in vcs if "vcs.revision=" in l), None)
mod = next((l.split("vcs.modified=")[1].strip() for l in vcs if "vcs.modified=" in l), None)
return rev, mod, len(vcs), len(out.splitlines())
def step_gate(args):
"""Refuses; never warns. Three conditions on the stamp, not one — a binary built in a linked
worktree carries NO vcs.* lines, and a gate that only forbids modified=true passes it always."""
bad = []
print("=== binaries: the frozen revision, a clean tree, and a stamp that EXISTS ===")
for b in BINARIES:
p = os.path.join(W, "bin", b)
rev, mod, n, total = stamp(p)
print(f" {b:<14} revision={(rev or 'none')[:12]} modified={mod} vcs_lines={n} (control: build lines {total})")
if n == 0:
bad.append(f"{b}: no vcs.* lines — this gate would be vacuous")
if rev != FREEZE_SHA:
bad.append(f"{b}: revision is not the frozen one")
if mod != "false":
bad.append(f"{b}: built from a dirty tree")
print("=== the stand is MINE: a pid on the port, not a 200 from someone else's process ===")
listeners = subprocess.run(["ss", "-ltnp"], capture_output=True, text=True).stdout.splitlines()[1:]
def owner(port):
for l in listeners:
if f":{port} " in l:
return l.split("users:")[-1] if "users:" in l else l
return None
for port, must in ((ADDR.split(":")[1], True),):
o = owner(port)
print(f" port {port}: {o or 'NOTHING LISTENS'}")
if must and not o:
bad.append(f"port {port}: there is no stand")
stub = owner("11434")
phase = args.phase
if phase == "smoke":
print(f" port 11434 (the $0 stub, REQUIRED in this phase): {stub or 'NOTHING LISTENS'}")
if not stub:
bad.append("the smoke has no stub to answer the local provider")
else:
if stub:
bad.append("something LISTENS on the local provider's address during a PAID phase: a "
"configuration that still reached a local model would be served free prose "
"and the run would measure nothing while looking green")
print(f" port 11434: ⛔ {stub}")
else:
print(f" port 11434: nothing listens (control: {len(listeners)} listeners on this host) "
f"— a local model would now fail LOUDLY")
print("=== what will be bought: sha256 of every file that decides it ===")
for name, path, expect in args.sha or []:
if not os.path.exists(path):
print(f" {name}: ABSENT at {path}")
bad.append(f"{name} is absent")
continue
got = subprocess.run(["sha256sum", path], capture_output=True, text=True).stdout.split()[0]
ok = (expect == "" or expect == got)
print(f" {name}: {got}{'' if expect == '' else (' (matches the pre-registration)' if ok else ' ⛔ the pre-registration says ' + expect)}")
if not ok:
bad.append(f"{name}: sha does not match the pre-registration")
print("=== the freeze tree itself ===")
head = subprocess.run(["git", "-C", os.path.join(W, "freeze"), "rev-parse", "HEAD"],
capture_output=True, text=True).stdout.strip()
dirty = subprocess.run(["git", "-C", os.path.join(W, "freeze"), "status", "--porcelain"],
capture_output=True, text=True).stdout.splitlines()
print(f" HEAD {head} dirty lines: {len(dirty)}")
if os.path.exists("/tmp/.git"):
bad.append("/tmp/.git exists: a build here would lose its stamp")
if bad:
for b in bad:
print("" + b)
raise SystemExit("THE GATE REFUSED; nothing was bought")
print("GATE PASSED")
# ---------------------------------------------------------------- the steps
def step_capabilities(args):
code, body, _ = api("GET", "/v0/capabilities", label="capabilities")
print(f"HTTP {code}")
print(json.dumps(body, ensure_ascii=False, indent=2)[:2000])
if code != 200:
# "empty list" and "never answered" are the same shape in output, and this is the pin the
# whole export third of the path hangs on: say which one happened.
print(f"⛔ the capabilities probe did not answer 200 — export_formats is UNKNOWN, not empty")
return
fmts = (body or {}).get("export_formats")
print(f"export_formats = {fmts!r}{'NON-EMPTY: all three export gates are satisfied' if fmts else '⛔ EMPTY: the export doors are not mounted and the last third of the path does not exist'}")
def step_login(args):
code, body, _ = api("POST", "/auth/dev-login", label="dev-login")
print(f"dev-login HTTP {code}")
rows = sql("select id, coalesce(email,'(none)') from users order by created_at desc limit 5")
print(f" users on this stand: {len(sql('select id from users'))} (control) — newest: {rows[0] if rows else 'none'}")
if rows:
state_put(user=rows[0][0])
print(f" remembered user={rows[0][0]}")
def step_grant(args):
out = subprocess.run([os.path.join(W, "bin", "tmplatformctl"), "grant",
"--user", args.user, "--usd", args.usd, "--note", args.note],
capture_output=True, text=True)
print(out.stdout.strip() or out.stderr.strip())
bal = subprocess.run([os.path.join(W, "bin", "tmplatformctl"), "balance", "--user", args.user],
capture_output=True, text=True)
print(bal.stdout.strip() or bal.stderr.strip())
rows = sql(f"select kind, amount_micro_usd from credit_ledger where user_id='{args.user}' order by id")
total = sum(int(r[1]) for r in rows)
print(f" ledger rows for THIS account: {len(rows)} (control: rows for ALL accounts "
f"{len(sql('select id from credit_ledger'))}), sum {total} µUSD")
def step_intake(args):
body, ct = multipart({"title": args.title, "source_lang": "zh", "target_lang": "ru"}, "file", args.path)
code, b, _ = api("POST", "/v0/books", body=body, ctype=ct, label="intake")
print(f"HTTP {code}\n{json.dumps(b, ensure_ascii=False, indent=2)}")
if code != 201:
raise SystemExit("the intake refused")
state_put(book=b["id"])
sha = subprocess.run(["sha256sum", args.path], capture_output=True, text=True).stdout.split()[0]
print(f" remembered book={b['id']} source sha256={sha}")
def _await_options(book, tries=20):
for _ in range(tries):
code, b, _ = api("GET", f"/v0/books/{book}/run-options", label="run-options")
if code == 200 and isinstance(b, dict) and "order" in b:
return code, b
time.sleep(3)
return code, b
def step_options(args):
book = state_load()["book"]
code, b = _await_options(book)
print(f"HTTP {code}\n{json.dumps(b, ensure_ascii=False, indent=2)}")
order = (b or {}).get("order", {})
ch = order.get("chapters_left")
funded = order.get("term_consistency_funded")
units = sql1(f"select count(*) from units u join chapters c on c.id=u.chapter_id where c.book_id='{book}'", "0")
per = sql(f"""select c.number, count(u.id) from chapters c left join units u on u.chapter_id=c.id
where c.book_id='{book}' group by 1 order by 1""")
for n, k in per:
print(f" chapter {n}: units {k}")
print(f" units_total = {units} chapters_left = {ch} term_consistency_funded = {funded}")
bad = []
if args.chapters is not None and str(ch) != str(args.chapters):
bad.append(f"the slice is not the pre-registered one: {ch} chapters, expected {args.chapters}")
if args.units is not None and str(units) != str(args.units):
bad.append(f"units_total is {units}, the pre-registration says {args.units}")
if args.require_funded and funded is not True:
bad.append(f"term_consistency_funded is {funded}: the hold does not carry the book bond and "
f"the run would quietly lose its terminology consolidation")
if bad:
for x in bad:
print("" + x)
raise SystemExit("STOP before paying")
print("OPTIONS ACCEPTED")
def step_start(args):
book = state_load()["book"]
payload = {"stop_for_signing": bool(args.stop_for_signing)}
if args.chapters is not None:
payload["chapters"] = args.chapters
if args.characters is not None:
payload["characters"] = args.characters
hdrs = {"Idempotency-Key": args.idempotency_key} if args.idempotency_key else {}
code, b, _ = api("POST", f"/v0/books/{book}/runs", body=json.dumps(payload).encode(),
ctype="application/json", label="start", headers=hdrs)
print(f"HTTP {code}\nrequest: {json.dumps(payload, ensure_ascii=False)}\n{json.dumps(b, ensure_ascii=False, indent=2)}")
if code in (200, 201, 202) and isinstance(b, dict) and b.get("id"):
state_put(run=b["id"])
print(f" remembered run={b['id']}")
return code, b
def _engine_numbers(book):
"""What the ENGINE's own project database says. Returns (rows, calls, free_replays, µUSD)."""
d = sql1(f"select workdir from books where id='{book}'")
db = os.path.join(d, "project.db") if d else None
if not db or not os.path.exists(db):
return None, db
import sqlite3
c = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
rows = c.execute("select count(*) from request_log").fetchone()[0]
hits = c.execute("select count(*) from request_log where tm_hit=1").fetchone()[0]
cost = c.execute("select coalesce(sum(cost_usd),0) from request_log").fetchone()[0]
per = c.execute("""select substr(trace_id,-1), count(*), sum(tm_hit=1), round(sum(cost_usd)*1e6)
from request_log group by 1 order by 1""").fetchall()
c.close()
return {"rows": rows, "calls": rows - hits, "free": hits, "micro": round(cost * 1e6),
"per_attempt": per}, db
def step_watch(args):
"""Liveness is read ACTIVELY and on two axes, per ATTEMPT. A growing log is not a result, and a
hung job is indistinguishable from a running one by the completion signal alone."""
s = state_load()
book, run = s.get("book"), s.get("run")
eng, db = _engine_numbers(book)
att = sql(f"select attempt_no, last_seq, coalesce(exit_code,-999), coalesce(spend_micro_usd,0), unit_name "
f"from run_attempts where run_id='{run}' order by attempt_no")
status = sql1(f"select status from runs where id='{run}'", "?")
paused = sql1(f"select coalesce(paused_reason,'') from runs where id='{run}'", "")
events = sql1(f"select count(*) from book_events where book_id='{book}'", "0")
user = s.get("user")
settled = sql1(f"select coalesce(sum(amount_micro_usd),0) from credit_ledger where user_id='{user}' and kind='settlement'", "0")
bal = sql1(f"select coalesce(sum(amount_micro_usd),0) from credit_ledger where user_id='{user}'", "0")
print(f"{time.strftime('%H:%M:%S')} status={status} paused={paused!r} book_events={events}")
for a in att:
unit = a[4]
active = subprocess.run(["systemctl", "--user", "show", unit + ".service", "-p", "ActiveState", "--value"],
capture_output=True, text=True).stdout.strip() or "unknown"
print(f" attempt {a[0]}: last_seq={a[1]} exit={a[2]} spend(cumulative)={a[3]}µUSD unit={active}")
if eng:
print(f" engine: request_log rows={eng['rows']} (calls={eng['calls']}, free replays={eng['free']}) "
f"spend={eng['micro']}µUSD | per attempt {eng['per_attempt']}")
else:
print(f" engine: no project database yet at {db}")
print(f" platform ledger for THIS account: settlements={settled}µUSD balance={bal}µUSD "
f"(control: ledger rows for ALL accounts {sql1('select count(*) from credit_ledger','0')})")
if paused:
raise SystemExit(f"paused_reason={paused} — STOP and ping the orchestrator")
if eng and args.stop_at and eng["micro"] >= args.stop_at:
raise SystemExit(f"spend reached {eng['micro']}µUSD ≥ {args.stop_at}µUSD — STOP and ping the orchestrator")
def step_evidence(args):
"""Takes the copy the resume DESTROYS. exportBank is called again at the start of the next
attempt and overwrites .bank.json atomically; .bank-stop.txt is truncated before it is rewritten,
so it can only be read while the run stands still."""
s = state_load()
book, run = s["book"], s["run"]
d = sql1(f"select workdir from books where id='{book}'")
out = os.path.join(W, "evidence", args.label)
os.makedirs(out, exist_ok=True)
status = sql1(f"select status from runs where id='{run}'", "?")
print(f"run status while the copy is taken: {status}")
if status == "translating":
raise SystemExit("the run is still moving: .bank-stop.txt would be read half-written")
n = 0
for f in ("project.db.bank.json", "project.db.mined-signature.yaml", "project.db.bank-stop.txt",
"project.db.auto-bank.yaml", "project.db.manifest.json", "events.jsonl",
"project.db.mined-delta.yaml", "project.db"):
src = os.path.join(d, f)
if os.path.exists(src):
subprocess.run(["cp", src, out + "/"])
sha = subprocess.run(["sha256sum", src], capture_output=True, text=True).stdout.split()[0]
print(f" {f}: {sha}")
n += 1
print(f" copied {n} artefacts (control: the book directory holds {len(os.listdir(d))} entries)")
code, b, _ = api("GET", f"/v0/books/{book}/bank", label=f"bank-{args.label}")
t = (b or {}).get("terms") or []
print(f" GET bank: HTTP {code}, terms={len(t)}, total={(b or {}).get('total')}, signed={(b or {}).get('signed')}")
rows = sql(f"select count(*), count(*) filter (where status='approved') from bank_terms where book_id='{book}'")
print(f" bank_terms in Postgres: all={rows[0][0]} approved={rows[0][1]}")
def step_sign(args):
book = state_load()["book"]
doc = open(args.doc, "rb").read()
code, b, _ = api("POST", f"/v0/books/{book}/bank/corrections", body=doc,
ctype="application/json", label=f"corrections-{args.label}")
print(f"HTTP {code}\nrequest: {doc.decode('utf-8')[:800]}\n{json.dumps(b, ensure_ascii=False, indent=2)[:2000]}")
return code, b
def step_resume(args):
run = state_load()["run"]
hdrs = {"Idempotency-Key": args.idempotency_key} if args.idempotency_key else {}
code, b, _ = api("POST", f"/v0/runs/{run}/resume", body=b"{}", ctype="application/json",
label=f"resume{args.label}", headers=hdrs)
print(f"HTTP {code}\n{json.dumps(b, ensure_ascii=False, indent=2)}")
return code, b
def step_export(args):
book = state_load()["book"]
code, b, _ = api("POST", f"/v0/books/{book}/exports", body=json.dumps({"format": args.format}).encode(),
ctype="application/json", label=f"export-{args.format}-order")
print(f"order HTTP {code}: {json.dumps(b, ensure_ascii=False)}")
if code not in (200, 201, 202):
raise SystemExit("the export door refused")
eid = b["id"]
for _ in range(60):
time.sleep(2)
code, b, _ = api("GET", f"/v0/books/{book}/exports/{eid}", label=f"export-{args.format}-status")
st = (b or {}).get("state")
if st == "ready":
break
if st == "failed":
raise SystemExit(f"the export failed: {b}")
print(f"status HTTP {code}: {json.dumps(b, ensure_ascii=False)}")
code, data, _ = api("GET", f"/v0/books/{book}/exports/{eid}/content",
label=f"export-{args.format}-content", raw=True)
os.makedirs(os.path.join(W, "evidence", "file"), exist_ok=True)
p = os.path.join(W, "evidence", "file", f"book.{args.format}")
open(p, "wb").write(data)
sha = subprocess.run(["sha256sum", p], capture_output=True, text=True).stdout.split()[0]
print(f"download HTTP {code} bytes={len(data)} sha256={sha}")
row = sql(f"select id,format,state,complete,size_bytes from exports where id='{eid}'")
print(f" exports row (complete is read from Postgres — the wire deliberately does not carry it): {row}")
def step_probe(args):
"""One ad-hoc request whose EXPECTED outcome was written down before it was sent (§4.8): a door's
answer read after the fact always looks right."""
body = args.body.encode() if args.body else None
hdrs = {}
if args.idempotency_key:
hdrs["Idempotency-Key"] = args.idempotency_key
if args.header:
for h in args.header:
k, _, v = h.partition(":")
hdrs[k.strip()] = v.strip()
code, b, hh = api(args.method, args.path, body=body,
ctype=("application/json" if body else None), label=args.label, headers=hdrs)
verdict = "AS EXPECTED" if (args.expect_status is None or code == args.expect_status) else "⛔ NOT AS EXPECTED"
print(f"[{args.label}] {args.method} {args.path} -> HTTP {code} (expected {args.expect_status}) {verdict}")
txt = json.dumps(b, ensure_ascii=False)[:600] if not isinstance(b, bytes) else f"<{len(b)} bytes>"
print(f" body: {txt}")
if args.expect_contains and args.expect_contains not in txt:
print(f" ⛔ the answer does not contain {args.expect_contains!r}")
def main():
ap = argparse.ArgumentParser()
sub = ap.add_subparsers(dest="cmd", required=True)
g = sub.add_parser("gate"); g.add_argument("--phase", default="paid"); g.set_defaults(f=step_gate, sha=[])
for name, fn in (("capabilities", step_capabilities), ("login", step_login)):
p = sub.add_parser(name); p.set_defaults(f=fn)
p = sub.add_parser("grant"); p.add_argument("--user", required=True); p.add_argument("--usd", required=True)
p.add_argument("--note", default="cold run B"); p.set_defaults(f=step_grant)
p = sub.add_parser("intake"); p.add_argument("--path", required=True); p.add_argument("--title", required=True)
p.set_defaults(f=step_intake)
p = sub.add_parser("options"); p.add_argument("--chapters", type=int); p.add_argument("--units", type=int)
p.add_argument("--require-funded", action="store_true"); p.set_defaults(f=step_options)
p = sub.add_parser("start"); p.add_argument("--chapters", type=int); p.add_argument("--characters", type=int)
p.add_argument("--stop-for-signing", action="store_true"); p.add_argument("--idempotency-key")
p.set_defaults(f=step_start)
p = sub.add_parser("watch"); p.add_argument("--stop-at", type=int, default=1000000); p.set_defaults(f=step_watch)
p = sub.add_parser("evidence"); p.add_argument("--label", default="evidence"); p.set_defaults(f=step_evidence)
p = sub.add_parser("sign"); p.add_argument("--doc", required=True); p.add_argument("--label", default="sign")
p.set_defaults(f=step_sign)
p = sub.add_parser("resume"); p.add_argument("--label", default=""); p.add_argument("--idempotency-key")
p.set_defaults(f=step_resume)
p = sub.add_parser("export"); p.add_argument("--format", required=True); p.set_defaults(f=step_export)
p = sub.add_parser("probe"); p.add_argument("--method", default="GET"); p.add_argument("--path", required=True)
p.add_argument("--body"); p.add_argument("--label", required=True); p.add_argument("--expect-status", type=int)
p.add_argument("--expect-contains"); p.add_argument("--idempotency-key")
p.add_argument("--header", action="append"); p.set_defaults(f=step_probe)
a = ap.parse_args()
a.f(a)
if __name__ == "__main__":
main()

127
eval/cold_run_b/measure.py Normal file
View file

@ -0,0 +1,127 @@
#!/usr/bin/env python3
"""measure.py — the money instrument of cold run B, pre-registered before the first cent.
It answers ONE question the call-level numbers cannot: what share of a book's UNITS was paid for and
not kept. A unit is `(chapter, chunk_idx, stage, role)` and it SPANS attempts on purpose the
attempt number is deliberately not in the key, because "we bought this cell twice" is a statement
about the cell, not about a retry.
Why it is validated against run A before it is pointed at run B: an instrument that has never
reproduced a known number is indistinguishable from one that reads the wrong column. `--selfcheck`
re-derives run A's published figures from run A's own database and refuses if any of them moves.
TWO THINGS THIS FILE REFUSES TO DO, both because a previous shift got a beautiful false number
from doing them:
* it never decides "which answer was accepted" by ORDER IN TIME. `ts` in this schema is second-
granular while `started_at` carries microseconds, so every checkpoint substitution is formally
"before" the attempt that made it. The attempt number is NAMED in `trace_id`; it is read, not
computed.
* it never calls the second numerator "over-payment". On run A the second terminology pass changed
the rendering of 12 terms out of 69 and 11 of those 12 are what the reader actually got. That is
the PRICE OF RE-MINING THE BANK AFTER A HUMAN SIGNATURE, not money bought for nothing.
"""
import argparse, collections, json, sqlite3, sys
def load(db):
c = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
c.row_factory = sqlite3.Row
rows = [dict(r) for r in c.execute("select * from request_log order by id")]
c.close()
return rows
def attempt_of(row):
"""The attempt is NAMED in the row. `trace_id` ends with it (tm-stream-<run>-<n>)."""
t = row.get("trace_id") or ""
tail = t.rsplit("-", 1)[-1]
return tail if tail.isdigit() else "?"
def measure(rows):
total = sum(r["cost_usd"] for r in rows)
units = collections.defaultdict(list)
for r in rows:
units[(r["chapter"], r["chunk_idx"], r["stage"], r["role"])].append(r)
paid = {k: v for k, v in units.items() if sum(x["cost_usd"] for x in v) > 0}
n1 = {k: v for k, v in units.items() if any(x["ok"] == 0 and x["cost_usd"] > 0 for x in v)}
n1_money = sum(x["cost_usd"] for v in n1.values() for x in v if x["ok"] == 0 and x["cost_usd"] > 0)
n2 = {k: v for k, v in units.items()
if sum(1 for x in v if x["ok"] == 1 and x["cost_usd"] > 0) > 1}
# Both orientations are printed. Neither is derived from time: rows are ordered by the primary
# key, and which one is called "the extra" is a DECLARED choice, not a discovered fact.
but_first = 0.0
but_last = 0.0
for v in n2.values():
acc = sorted([y for y in v if y["ok"] == 1 and y["cost_usd"] > 0], key=lambda z: z["id"])
but_first += sum(x["cost_usd"] for x in acc[1:])
but_last += sum(x["cost_usd"] for x in acc[:-1])
by_degraded = sum(r["cost_usd"] for r in rows if (r["degraded"] or "") != "")
by_ok0 = sum(r["cost_usd"] for r in rows if r["ok"] == 0 and r["cost_usd"] > 0)
free = [r for r in rows if r["tm_hit"] == 1]
per_attempt = collections.Counter(attempt_of(r) for r in rows)
return dict(rows=len(rows), calls=len(rows) - len(free), free=len(free), total=total,
units=len(units), paid_units=len(paid), n1=len(n1), n1_money=n1_money,
n2=len(n2), n2_but_first=but_first, n2_but_last=but_last,
by_degraded=by_degraded, by_ok0=by_ok0, per_attempt=dict(per_attempt),
n1_keys=sorted(n1), n2_keys=sorted(n2))
def report(m, label):
def pct(x):
return f"{100 * x / m['total']:.1f}%" if m["total"] else "n/a"
print(f"=== {label} ===")
print(f" rows={m['rows']} calls={m['calls']} free checkpoint replays={m['free']} "
f"total=${m['total']:.6f} per attempt (from trace_id): {m['per_attempt']}")
print(f" UNITS total={m['units']} paid (sum cost>0)={m['paid_units']} "
f"{'⚠ the paid filter removed nothing — its behaviour is still unexercised' if m['units'] == m['paid_units'] else 'the paid filter DID remove units'}")
print(f" NUMERATOR 1 — a paid attempt that was not kept: {m['n1']} of {m['paid_units']} units = "
f"{100 * m['n1'] / m['paid_units']:.1f}% of units (money ${m['n1_money']:.6f} = {pct(m['n1_money'])})")
for k in m["n1_keys"]:
print(f" {k}")
print(f" NUMERATOR 2 — a cell whose KEPT result was paid for more than once: {m['n2']} units")
print(f" all payments but the FIRST = ${m['n2_but_first']:.6f} = {pct(m['n2_but_first'])} <- the declared number (lower bound)")
print(f" all payments but the LAST = ${m['n2_but_last']:.6f} = {pct(m['n2_but_last'])} <- the other orientation, printed beside it")
for k in m["n2_keys"]:
print(f" {k}")
print(f" MONEY, path 1 (degraded <> '') = ${m['by_degraded']:.6f} = {pct(m['by_degraded'])}")
print(f" MONEY, path 2 (ok=0 AND cost>0) = ${m['by_ok0']:.6f} = {pct(m['by_ok0'])}")
print(f" {'the two paths AGREE — a weak sign: on run A they agree BY CONSTRUCTION' if abs(m['by_degraded'] - m['by_ok0']) < 1e-9 else '⛔ the two paths DISAGREE — a STRONG finding: the classifier and the acceptance mark have come apart'}")
SELFCHECK = {"rows": 33, "calls": 27, "free": 6, "units": 17, "paid_units": 17, "n1": 4, "n2": 6}
SELFCHECK_MONEY = {"total": 0.419423, "by_degraded": 0.106472, "by_ok0": 0.106472,
"n2_but_first": 0.028742, "n2_but_last": 0.035723}
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--db", required=True)
ap.add_argument("--label", default="run")
ap.add_argument("--selfcheck", action="store_true",
help="the db is run A's: refuse unless every published figure of run A comes back")
ap.add_argument("--json")
a = ap.parse_args()
m = measure(load(a.db))
report(m, a.label)
if a.json:
with open(a.json, "w") as f:
json.dump({k: v for k, v in m.items() if k not in ("n1_keys", "n2_keys")}, f, indent=2)
if a.selfcheck:
bad = [f"{k}: {m[k]} != {v}" for k, v in SELFCHECK.items() if m[k] != v]
bad += [f"{k}: {m[k]:.6f} != {v:.6f}" for k, v in SELFCHECK_MONEY.items()
if abs(m[k] - v) > 5e-7]
if bad:
print("⛔ SELFCHECK FAILED — this instrument does not reproduce run A:")
for b in bad:
print(" " + b)
sys.exit(1)
print("SELFCHECK PASSED: every published figure of run A comes back from its own database.")
if __name__ == "__main__":
main()