From 8e475fa2eb05eb9f0ebfd7f6ab2000f68a4d4e71 Mon Sep 17 00:00:00 2001 From: heaven Date: Mon, 31 Aug 2026 01:43:25 +0300 Subject: [PATCH] Issue the cold-run prompt under the owner's sanctioned budget and repair the money doc, whose fifty-one pointers were re-checked and whose form now survives drift --- docs/BACKEND_COLDRUN_V16_SESSION_PROMPT.md | 550 +++++++++++++++++++++ docs/PROGRESS.md | 4 +- docs/README.md | 2 +- docs/architecture/05-decisions-index.md | 3 +- docs/architecture/05-decisions-log.md | 14 +- docs/architecture/15-money-path.md | 63 +-- 6 files changed, 603 insertions(+), 33 deletions(-) create mode 100644 docs/BACKEND_COLDRUN_V16_SESSION_PROMPT.md diff --git a/docs/BACKEND_COLDRUN_V16_SESSION_PROMPT.md b/docs/BACKEND_COLDRUN_V16_SESSION_PROMPT.md new file mode 100644 index 00000000..f2e18468 --- /dev/null +++ b/docs/BACKEND_COLDRUN_V16_SESSION_PROMPT.md @@ -0,0 +1,550 @@ +# Промт: ХОЛОДНЫЙ ПРОГОН ДВИЖКА на схеме v16 (бэкенд-сессия, ПЛАТНАЯ) + +> Выдан оркестратором №21, 31.08. Очередь — п.2 CURRENT-STATE в `docs/PROGRESS.md`. +> **ДЕНЬГИ САНКЦИОНИРОВАНЫ ВЛАДЕЛЬЦЕМ 31.08: ≈$0.60, потолок $0.80, ДЕСЯТЬ глав ПОЛНОЙ ЦЕПЬЮ +> включая редакторскую волну.** Прежняя цифра очереди «≈$0.05» снята как протухшая (§1в). +> ⚠ Промт прошёл два рубежа: механическую сверку 12 блоков и опровергатель в четыре линзы — +> 53 находки применены, 8 из них блокеры. Фактура ниже пере-проверена ИСПОЛНЕНИЕМ; где не смогли — +> сказано прямо. + +## 0. Какая проблема и что решит твой результат + +Движок переводит книги за деньги. С **27.08** в него легло семь коммитов и ~15 ратифицированных +решений: входная дверь шва, стоп банка ФЛАЖКОМ внутри движка, схема хранилища **v16**, полоса отказов +в два яруса с классом 15, объёмный потолок `--max-units`, гейт схемы, писатель книги (`tmctl build`). +**Ни одно из них не проверено деньгами.** + +Проверено исполнением: последний платный вызов движка в корпусе — **17.08** (5 вызовов классификатора +в `coldrun-b/reprobe/classify6`, $0.005582), всё остальное — до 02.08. Ни один существующий леджер не +стоит на сегодняшней схеме, и ни один не гонял ПОЛНУЮ цепь на HEAD. + +Значит все эти решения сегодня — обещания кода и тестов, а не факты о работающей системе. + +**Твой результат** — первый платный сквозной прогон на текущем HEAD, который отвечает на один вопрос: +**переводит ли движок книгу насквозь и отдаёт ли файл — на настоящих деньгах, настоящих ключах, +настоящем провайдере.** Не «зелёная батарея», а книга, которую можно открыть. + +Он же снимает гейт с чужих работ, ждущих его с 20.08: строки бэклога **202** (живой прогон через API +платформы) и **216** (подпись банка · halt на потолке · резюм после пополнения против ЖИВОГО движка). +Слово владельца 20.08: «гонять одну неготовую систему против другой смысла нет» — поэтому первым идёт +движок, один. Попутно и почти даром закрываются строки **154**, **157**, **160**, **198** (§5.7). + +⚠ **Качество перевода — НЕ твой предмет.** Художественную оценку делает полигон. Ты меряешь МЕХАНИКУ и +ДЕНЬГИ. Границы предмета: ты не судишь, хорош ли перевод, не калибруешь ставку платформы и не трогаешь +дверь выдачи (её в платформе просто нет — `createExport`/`getExport` не смонтированы). + +## 1. Что именно ты делаешь + +### 1а. Форма прогона — делай РОВНО так + +**Новый проект, новый каталог, новый `book_id`.** Ни один существующий стенд не переиспользуется. +Причины механические, все проверены исполнением: + +1. **Схема.** Проектные БД корпуса стоят на **v7–v15** (на v14 — 13 из 36, одна на v15), бинарь HEAD — + на **v16**; число применяемых версий у каждого стенда своё. Мигрировать чужой стенд ради холодного + прогона — значит необратимо тронуть чужую улику. +2. **Конфиг.** Старый `book.yaml` с ключами `mined_delta:`/`mined_rejects:` **ОТКАЗЫВАЕТСЯ, exit 10** + (D39.158 п.5): пути решений теперь выводятся конвенцией `<каталог>/.mined-delta.yaml`. + Такие ключи несут 17 файлов корпуса, включая оба прежних холодных прогона. +3. **Потолки.** Потолки существующих стендов пробиты новой ценой и защищать перестали (`coldrun-a` + `book_usd: 0.25` против пересчитанных ≈$0.56). + +**Каталог:** `books/gu-zhenren/coldrun-v16/` — создаёшь ты. + +**Исходник:** `books/gu-zhenren/coldrun-a/guzhenren-ch1-10.gb18030.txt` +(sha256 `0b5f9b0266d8…`, 28 174 знака без переводов строк, 10 глав). Копируешь к себе, оригинал не +трогаешь. Тождество байтов выбрано НАРОЧНО: **ровно этот файл лежит и под `bank-low`, и под `minirun`**, +то есть под обоими историческими базлайнами, с которыми ты сравниваешь деньги (§5.3). + +**`pipeline.yaml` — копия `coldrun-b/reprobe/bank-low/pipeline.yaml`** с починенными по §1б путями. +Любое отличие выпиши в записку-план ПОСТРОЧНО: сравнимость денег держится на конфиге, а не только на +байтах исходника. **`escalation.budget_usd` обязан быть > 0** — в репозиторном шаблоне он 0, и тогда +«эскалационные хопы» §5.3 и запас $0.60→$0.80 меряют пустоту. **Сид: идём БЕЗ `glossary_seed`**, как +bank-low; решишь иначе — это пинг, а не выбор. + +### 1б. ⚠ ЛОВУШКА ПУТЕЙ — главная в этом паке, проверь ПЕРВЫМ делом + +На машине **ДВА чекаута** одного репозитория: + +| Путь | Ветка | Что там | +|---|---|---| +| `/home/ubuntu/projects/textmachine-main` | `main` | ТВОЙ. HEAD с паком писателя книги | +| `/home/ubuntu/projects/textmachine` | **`polygon`** | ЧУЖОЙ, ветка полигона | + +`books/` в твоём дереве — **СИМЛИНК** на `/home/ubuntu/projects/textmachine/books`; `/home/ubuntu/books` +— **тот же симлинк**. То есть корпус физически лежит в чужом чекауте и версионируется ОТДЕЛЬНЫМ git-репо. + +⚠ **`book.yaml` несёт СЕМЬ абсолютных путей под ДВУМЯ разными корнями.** +- Под `/home/ubuntu/projects/textmachine/` — только **`models:`** и **`langpack_root:`**. + ⇒ **ОБА переведи в `/home/ubuntu/projects/textmachine-main/backend/configs/…`.** +- Под `/home/ubuntu/books/` — `pipeline:`, `source_file:`, `project_db:`, `langpack_extend:`. + Это твой каталог прогона; они и должны смотреть туда. +- `prompt_override:` — если используешь, это `…-main/backend/prompts/`, НЕ `configs/`. + +⚠ **ЕДИНСТВЕННОЕ ИСКЛЮЧЕНИЕ — `mining.contrast_path`.** Jieba-контраст лежит ТОЛЬКО в +`/home/ubuntu/projects/textmachine/eval/exp16/data/jieba_dict_general_zh.txt` (5 МБ, gitignored); в +`textmachine-main` его нет, а репозиторный `pipeline-c1.yaml` секции `mining:` не несёт вовсе. Оставь +путь в polygon-дереве как есть (чтение чужого чекаута законно) либо скопируй файл к себе. **Без него +`--verify-bank` отказывает, и ось §5.4 закрывается целиком.** + +**Зачем всё это.** В polygon-дереве **нет каталога `langpacks/ru`** — он есть только в `main`. Два +чекаута `backend` различаются ТОЛЬКО этим: `models.yaml` и промпты совпадают побайтово, так что снапшот +прогона от выбора чекаута не зависит. Но `ru/reader.txt` — это слова, которые читатель видит на дыре. +`LoadReaderWords` на отсутствующий каталог возвращает дефолты **без ошибки**. + +**Проверки исполнением, обе обязательны:** +1. `ls -d <твой langpack_root>/ru/reader.txt` — ДО покупки. +2. Ось закрывается только вторым шагом из §5.1 (`--partial` на книге с дырой). ⚠ **На complete-книге + ловушка НЕВИДИМА:** сборка с `langpacks/ru` и без него даёт побайтно один файл и ни одного WARN. + +⚠ Дисциплина симлинка: `find books -name X` **без `-L` даёт ЛОЖНЫЙ НОЛЬ** (оркестратор на этом уже +попался). Всегда `find -L`. И никогда не создавай симлинков внутрь `books/`. + +### 1в. Деньги — санкция владельца, делай РОВНО так + +Очередь несла «≈$0.05». **Цифра устарела в ~4.5 раза:** DeepSeek пере-пинен коммитом **15.08** +(`76049bb`, D39.137) под вендорские цены, вступившие в силу **16.08 16:00 UTC** — flash +`input 0.14→0.44`, `output 0.28→1.32`; pro `0.435→1.32` / `0.87→3.96`. Заход `bank-low`, стоивший +$0.045095 в июльских деньгах, по этим ценам стоит **$0.2017** (множитель 4.47, пере-считано из его же +токенов). + +**Слово владельца 31.08: ≈$0.60, потолок $0.80, ДЕСЯТЬ глав, ПОЛНАЯ ЦЕПЬ включая редакторскую волну.** + +⚠ **«Потолок» здесь не абсолютный.** Он сверяется ДО каждого вызова против `committed + reserved`, но +против ОЦЕНКИ, а списывается факт — поэтому итог может выйти за $0.80 на величину порядка одной +максимальной резервации (`backend/internal/store/ledger.go` — «do not overshoot the ceiling by more +than one maximum reservation»). На этом объёме это центы; знать об этом обязана ты, а не отчёт. + +Обязательны все три ручки: + +- **`--ceiling-usd 0.80`** на `translate` — КНИЖНЫЙ КУМУЛЯТИВНЫЙ потолок, перекрывает `book.yaml`, + назад не пишется, обязан быть > 0; +- **`ceilings: { book_usd: 0.80, day_usd: 1.00 }`** в `book.yaml` — продублировать ОБА (Р7: конфиг + падает, если оба ≤ 0; день не должен срабатывать раньше книги); +- **`--max-units` — ДВУМЯ ПОРЦИЯМИ, и это несущее требование, а не оптимизация.** + - **Первая покупка:** `--max-units` заведомо малый (2–3 юнита). Обязана дать **exit 0** и строку + «Next: N output unit(s) … have never been delivered». Это **единственное живое предъявление + объёмного потолка** — механизма, который §0 называет среди проверяемых. При `--max-units` = + `units_total` он не сработает НИ РАЗУ и останется там же, где был: в тестах. + - **Вторая покупка:** остаток, `--max-units` = `units_total` из `tmctl manifest --json`. Уже + оплаченные юниты не считаются и не пере-оплачиваются. Сохрани WARN про майнящую банк книгу и + ЗАФИКСИРУЙ, потребовался ли `--resnapshot` — это живое основание `PD-422` и п.5 очереди. + +⚠ **$0.60 — ОЖИДАНИЕ, $0.80 — потолок. Между ними запас на ретраи и эскалационные хопы**, которые +объёмный потолок не тратит, а деньги тратят. **Упёрся в $0.80 — это exit 4, СТОП и ПИНГ, а не тихая +пере-нарезка скоупа.** Подъём потолка просят при СРАБАТЫВАНИИ стопа, не по проекции: прогнозный запрос +оставляет ложный след «владелец согласился на пере-оплату», которой не было. + +⚠ **Сметы ПЕРВОЙ покупки у движка НЕТ** — `--dry-run` у `translate` отвергается явно. Потолки и есть +твоя единственная защита. Смета ПЕРЕ-прохода существует, она $0 и без ключей. + +⚠ **Гони в ДОЛИНУ.** У DeepSeek цена ходит окнами: пики **UTC 01–04 и 06–10 вдвое дороже**. Цены в +`models.yaml` пинованы ПИКОВЫЕ, поэтому в долине леджер завысит — это в безопасную сторону. **Время +старта запиши ПОЛЕМ в артефакт:** перенос машины обнулил mtime всего сырья, на mtime не опирается никто. + +### 1г. Состав прогона + +Гони **полную цепь, включая редакторскую волну, на ДЕСЯТИ главах** — объём и состав заданы словом +владельца (§1в), тобой не выбираются. + +Довод, почему не draft-only, и он честный: проверено исполнением по всем **36** базам корпуса — роль +`judge` не вызывалась **НИ РАЗУ нигде**, а редакторская волна **гонялась и оплачена** на малых +прогонах (19 баз), но **НЕ на холодном старте**: у `coldrun-a` и во всей семье `coldrun-b` ноль +редакторских вызовов. Именно это держит строку бэклога **16**. Не измерена не «редактура вообще», а +ПОЛНАЯ ЦЕПЬ ОДНИМ ЗАХОДОМ на холодном старте: draft+терминолог и edit меряли разные стенды под разными +конфигами. + +Десять глав выбраны ради сравнимости: тот же байтовый исходник и тот же объём, что у обоих исторических +базлайнов, поэтому множитель «июль → сегодня» получается без экстраполяции (§5.3). + +⚠ Приор оркестратора, **опровергается твоим замером**: draft+терминолог ≈$0.20, редакторская волна на +тех же десяти главах ≈$0.38–0.40, итого ≈$0.58 при потолке $0.80. Не влезает — **стоп и пинг, не режь +скоуп молча**. + +## 2. Зона записи и git + +**Пишешь:** `books/gu-zhenren/coldrun-v16/` (каталог создаёшь) + отчёт сессии. + +**Не пишешь:** `backend/`, `platform/`, `frontend/`, `eval/`. **`docs/` — тоже не пишешь**, и это +ОСОЗНАННОЕ сужение канона на этот пак: `CLAUDE.md` разрешает бэкенд-сессии писать итог в свою секцию +`docs/PROGRESS.md`, но здесь итог и пинги едут ОТЧЁТОМ, а в `PROGRESS.md` и `archive/reports/` их кладёт +оркестратор — иначе твоя строка уедет чужим коммитом из живого дерева. Пинг = секция отчёта либо +прямой канал §12. + +- **Ты НЕ коммитишь.** Ни в этот репозиторий, ни в репозиторий книг. Лендит оркестратор. +- ⛔ **Книга и производные в репозиторий `textmachine` не коммитятся НИКОГДА** (CLAUDE.md). +- ⛔ **НИКОГДА:** `git add -A` · `git add .` · `git commit -a` · `reset --hard` · `checkout` поверх + грязного дерева · перезапись истории · `git stash` (стек общий с другими сессиями). +- В дереве лежит незакоммиченная работа ДРУГИХ живых сессий (`platform/**`) — **не трогать вообще**. + +⚠ **Прогон НЕИЗБЕЖНО пачкает репозиторий КНИГ, и роли для него в проекте не определено.** Тот +репозиторий ОТСЛЕЖИВАЕТ `.db` (и `-wal`/`-shm`), а `.book.epub`/`.book.txt` его `.gitignore` не +покрывает; `tmctl migrate` переписывает отслеживаемый файл БД. В ролевой таблице `CLAUDE.md` зоны +`books` **нет вообще**. ⇒ **Ничего там не коммить и не прибирать.** Оставь `git -C books status` +грязным, назови его состояние в отчёте дословно, вопрос «кто коммитит книги» подними пингом. + +⛔ **И ЧИТАТЬ чужие БД не безобидно.** `sqlite3 …` **без `?mode=ro`** открывает базу НА ЗАПИСЬ, +чекпойнтит WAL и УДАЛЯЕТ отслеживаемые `-wal`/`-shm`. Проверено исполнением при подготовке пака: одна +SELECT-выборка по `coldrun-a` оставила две строки ` D` в чистом до того репозитории. ⇒ **Любое чтение +чужой базы — только `sqlite3 'file:<путь>?mode=ro'`.** + +**Нашёл баг движка — ПИШИ, не чини.** Правка `backend/` — отдельный пак. Исключение ровно одно: если +без однострочной правки прогон физически не стартует, ты **пингуешь** и ждёшь, а не патчишь молча. + +## 3. Карта чтения — ЗАКОН, пять позиций + +1. **`docs/experiments/00-provider-quirks.md`** — ЦЕЛИКОМ и ДО первого платного вызова. Обязательное + пре-чтение перед любым обращением к провайдеру: живые слаги, thinking, `finish_reason`, + игнорируемые параметры. ⚠ Проба падает или ведёт себя странно ⇒ **идём в вендор-доку, НЕ гадаем**. +2. **`docs/architecture/18-bank-ontology.md`** — ратифицирована D39.158, обязательное пре-чтение перед + всем, что трогает банк. Три роли носителей · единственные писатели · дисциплина проекции. +3. **`backend/README.md`** — механика движка: волны, стадии, чанкование, артефакты. +4. **`docs/architecture/15-money-path.md` §2 «Деньги ДВИЖКА»** — леджер, оба потолка, leftover-reserved. + ⚠ Якоря дока пере-проверены и починены 31.08, им можно верить. +5. **`docs/architecture/17-seam-inbound-law.md`** — закон входной двери шва. + +Всё остальное — код первичен, достаёшь сама грепом. Ратифицированное вложено в тело этого промта. + +## 4. Разметка свободы + +**Делай РОВНО так (отступление = пинг оркестратору, не интерпретация):** + +- новый каталог, новый `book_id`, `pipeline.yaml` — копия bank-low с `escalation.budget_usd > 0`, + без сида; чужие стенды не мигрировать и не переиспользовать (§1а); +- пути: `models:` и `langpack_root:` — в `textmachine-main`; `mining.contrast_path` — исключение (§1б); +- **число глав ДЕСЯТЬ и `--max-units` двумя порциями — санкция владельца (§1в), не твой выбор.** + ⚠ Меньшее N на ВТОРОЙ покупке даёт остановку по объёму, а она печатает **exit 0** — ШТАТНОЕ + ЗАВЕРШЕНИЕ, неотличимое в коде от «книга переведена». Скоуп молча усохнет; +- бинарь `tmctl` **собрать из HEAD** своего дерева и записать `sha256`. Причина не в том, что стендовый + `~/.local/bin/tmctl` чего-то не умеет — он от 30.08 23:17 и пак писателя книги в нём есть, а на 31.08 + он побайтно совпадает с HEAD-сборкой. Причина в том, что **личность бинаря, которым куплен прогон, + обязана быть зафиксирована числом**, а стенд пере-собирают чужие сессии без предупреждения; +- ключи — `--keys-file` **абсолютным** путём. Цепь резолва: окружение → `--keys-file` → `.env` рядом с + `book.yaml` → `./.env` в CWD. `--keys-file` **падает громко** на нечитаемом или пустом файле и + принимается **только `translate`**. Единственные заполненные файлы ключей на машине — + `/home/ubuntu/projects/textmachine/backend/.env` и `.../eval/.env` (576 Б, оба тронуты 30.08 01:01, + что совпадает со словом владельца «починил ключи 30.08»). ⛔ **Файлы ключей НЕ ОТКРЫВАТЬ** — ни тебе, + ни субагентам. Наполненность проверяется ПРОГОНОМ; +- оба денежных потолка выставлены до первого платного вызова (§1в); +- деньги считаются **из сырого леджера ДВУМЯ независимыми путями** (§6.3); +- **`make battery` в `backend/` ДО первой покупки.** Не `go test ./...` — батарея держит ещё + `vet -tags live`, `gofmt`, `golangci-lint` и `-race`, и только она гейт зоны. Скипы вслух; +- **`tmctl migrate` отдельным шагом** — что он применит и что печатает, см. §1а п.1 и §6.2; +- ⛔ **ПЕРВАЯ ЖЕ правка исходника закрывает платную часть НАВСЕГДА** — правило §5.6, и оно бьёт твою + свободу выбирать порядок фаз. + +**Решаешь сама и аргументируешь:** + +- порядок фаз внутри платной части и где ставить свои стоп-точки; +- нужен ли `--verify-bank` и в какой фазе (см. §5.4); +- какие дополнительные $0-пробы дёшевы и стоят того. + +## 5. Что прогон обязан предъявить — по осям + +### 5.1 Сквозной путь, структура глав и файл книги + +`translate` → `status` → `report` → `manifest --json` → `export --json --pairs` (и отдельно голый +`export` — это РАЗНЫЕ каналы) → **`build`**. + +**Шесть состояний честности D39.175 — это pending · withheld · incomplete · ghost · stale · drift, и +живут они в ТРЁХ разных местах**, отчего и разъезд счёта «шесть состояний, но пять дыр». Четыре — +перечисление `HoleKind` (`HolePending` · `HoleWithheld` · `HoleIncomplete` · `HoleStale`; пятая +константа `HoleNone` = дыры нет). Пятое, **ghost**, — не `HoleKind`, а счётчик `BookExport.GhostRows`: +переведённый текст, которому текущая нарезка не находит места; целостность он рубит наравне с дырами +(`complete := len(holes) == 0 && exp.GhostRows == 0`). Шестое, **drift**, — булево поле отчёта и WARN, +в файл не пишется. + +**Твой прогон штатно даст ОДНО:** `complete=true` либо `pending`. Остальные — $0-провокации ПОСЛЕ +платной части, и порядок у них жёсткий: **§5.6**. + +⚠ **`config_drift=false` и `stale_unknown=false` — самая дешёвая НОВАЯ фактура прогона.** Оба поля ни +разу не имели честного «нет»: все стендовые книги под дрейфом. **`stale_unknown=true` на свежей книге +означает СБОЙ, а не дрейф** — ищи его в WARN. ⇒ **Сохраняй stderr прогона целиком.** + +**Структура глав — узел строки бэклога 160, и он $0.** По `manifest --json` назови: сколько глав +получили `heading` из текста, а сколько — цифровой рендер движка; совпадает ли число глав с 10; что +напечатано меткой главы в собранных EPUB и txt. На листе владельца висит ровно этот вопрос (В-4/Ф-30, +лейбл главы без заголовка) — приложи **дословные метки**, а не пересказ. + +⚠ **Две ловушки `build`:** +1. `build --format epub` **МОЛЧА УДАЛЯЕТ** лежащий рядом `.book.txt` (и наоборот): набор рядом с БД + всегда от ОДНОЙ сборки. А `StatusArtifacts.book_files` публикует ПОЛНУЮ карту форматов независимо от + того, что записано. ⇒ **Собирай ОБА формата одной командой** (без `--format`) и не считай наличие + пути в `book_files` доказательством наличия файла. +2. После коммита файлов `build` удаляет копии незапрошенных форматов, и сбой этого `os.Remove` + возвращается ГОЛОЙ ошибкой ⇒ **exit 1 при том, что новые файлы УЖЕ на диске, а `BuildReport` не + печатается вовсе**. ⇒ **При ЛЮБОМ ненулевом коде посмотри, что реально лежит на диске.** + +⛔ **ПОРЯДОК СБОРКИ.** Чистая книга собирается и СОХРАНЯЕТСЯ **до** любой правки исходника: собери оба +формата одной командой, скопируй пару файлов в `artifacts/clean/` внутри каталога прогона — и только +потом провоцируй. Демонстрацию `--partial` гони с `--out` в отдельный путь, иначе она перезапишет НА +МЕСТЕ ровно тот файл, который ты отдаёшь владельцу. + +⚠ **Ось «издательский результат».** Клейм очереди «0 fatals / 0 errors на трёх стендовых EPUB» был «со +слов сессии»; **две трети пере-проверены независимо** при подготовке промта: `epubcheck` на машине НЕТ, +но java 21 есть и валидатор приносится одной командой; пере-собранный бинарём HEAD EPUB настоящего +перевода (стенд `minirun`) дал 0 fatals / 0 errors. Третья точка — **твой** EPUB. +⚠ Третий стенд (`acceptance`, единственный с `ghost`/`withheld`/`incomplete`) дёшево не пере-собирается +— его pipeline-конфиг протух по трём классам. Эти три состояния либо возникнут у тебя сами, либо +**останутся непроверенными, и так и запиши**. +⚠ **Живой читалки на машине нет ни одной.** Не изобретай: **передай файл владельцу** и назови ось +открытой до его ответа. + +### 5.2 Коды выхода — таблица, сверяй фактические + +| Код | Что случилось | +|---|---| +| 0 | чисто (в т.ч. остановка по `--max-units` — это ЗАВЕРШЕНИЕ) | +| 1 | инфраструктура, ошибка разбора флага, пойманная ПАНИКА | +| 2 | завершено С ФЛАГАМИ | +| 3 | стоп подписи банка (намеренная человеко-в-цикле пауза, НЕ падение) | +| 4 | halt по денежному потолку (книга или день) | +| 5 | graceful stop (пойман SIGINT/SIGTERM) | +| 10 | конфиг не поедет: нечитаем, неразбираем, невалиден, нет ключа | +| 11 | исходник КНИГИ не читается/не декодируется | +| 12 | проект залочен другим `tmctl` | +| 13 | схема проекта не этого бинаря — нужен `tmctl migrate` | +| 14 | документ решений прочитан и отклонён — решает ПОЛЬЗОВАТЕЛЬ | +| 15 | документ принят, запись не завершилась — отчёт называет, какой файл лёг; шли тот же документ снова | +| 16 | `build` отказался писать книгу с дырами — либо доделать, либо `--partial` | +| 19 | класс отказа, номера которому этот бинарь не знает | + +Каждый код, который ты увидишь, — с командой и фактическим `$?`. Кода, которого не видела, не называй. +⚠ **Exit 15 у `build` на этом прогоне НЕДОСТИЖИМ** без инъекции сбоя ФС между двумя `rename` — не +заказываю. Но знай: 15 означает, что ЧАСТЬ файлов уже на диске. + +### 5.3 Деньги + +Разложи стоимость по стадиям и ролям и положи на **ОБА** исторических базлайна — они меряли разное, и +только вместе покрывают твою цепь: + +- **`bank-low`** (`coldrun-b/reprobe/bank-low/guzhenren-bank-low.db`) — терминологический контур: + draft flash $0.021289 + draft pro $0.010350 = $0.031639 · терминолог $0.005750 · классификатор + $0.007706 · **весь заход $0.045095**; +- **`minirun`** (`books/gu-zhenren/minirun/guzhenren-minirun.db`) — РЕДАКТОРСКИЙ контур на том же + исходнике побайтно: draft flash $0.018834 (58) · draft pro $0.003371 (3) · **edit editor pro + $0.092086 (16)** · весь заход $0.114291. + +Оба — в ИЮЛЬСКИХ деньгах. Твоя задача — дать то же разложение в СЕГОДНЯШНИХ и назвать множитель. + +⚠ **Леджер = НИЖНЯЯ граница** (строка 78). «Оплаченный хвост» собирается из **ДВУХ таблиц**: +- по `checkpoints` — ретраи (`attempt>0`) и эскалационные хопы (`escalation=1`). ⚠ У `checkpoints` + **НЕТ `book_id`** — связь только через `JOIN jobs j ON c.job_id = j.id WHERE j.book_id=…`; +- по `request_log` — `ok=0 AND cost_usd>0` и `estimated=1`. ⚠ Колонок `attempt`/`escalation` в + `request_log` НЕТ, запрос по букве старой редакции упал бы. + +⛔ **НЕ путь к сумме:** `chapters[].cost_usd` из `status --json` — это стоимость только ПОСЛЕДНЕЙ +резолюции строки. + +⚠ **В денежных таблицах НЕТ идентификатора прогона:** `checkpoints` и `spend` пожизненные по книге, +единственная ось разделения прогонов в них — `checkpoints.created_at` (UTC). Ось прогона есть только в +телеметрии (`request_log.trace_id`). **Ты покупаешь ДВУМЯ порциями (§1в) — спланируй разбор заранее.** + +### 5.4 Банк и стоп подписи + +Стоп банка стал **флажком на НОВИЗНЕ** (D39.158 п.1): `--verify-bank` останавливает прогон перед +edit-волной **только** если в карте есть кластер, вся поверхность которого ещё не предъявлялась; память +предъявления — `bank_stop_presented`, схема v16. Прогон, который НЕ стопится, обязан свернуть +неподписанные строки в банк ПЕРЕД стадией. + +⚠ **Нота проверяется ДВУМЯ прогонами — реши сама, влезает ли.** Первая половина: первый `--verify-bank` +даёт РОВНО ОДИН стоп. Вторая: повторный `--verify-bank` **БЕЗ единого решения по термам НЕ стопится +второй раз** — это и есть новизна-как-флажок. Проверив только первую, ты проверишь ноту наполовину, и +так и напиши. Наблюдаемое: `exit 3` · строка «the delta holds terms no stop has shown before» · строки +в `bank_stop_presented` · файл `.mined-signature.yaml`. Провал записи памяти НЕ валит прогон — он +даёт один лишний доброкачественный стоп, и это штатно. + +Форматы, изменившиеся и ни разу не виденные на живых данных: `.mined-signature.yaml` получила +конверт `map_version: tm-signature-map-v1` + `map_id`; `.bank.json` — якорь свежести `as_of`. + +⚠ **Атомарность сайдкаров пере-проверена по коду 31.08:** через `writeFileAtomic` идут **ЧЕТЫРЕ** — +`.manifest.json`, `.bank.json`, `.mined-signature.yaml`, `.auto-bank.yaml`; неатомерен +**ОДИН** — `.bank-stop.txt` (`os.WriteFile` с усечением; это отрендеренная ТАБЛИЦА, не JSON). +**Файла `bank-stop.json` не существует.** Форматы книги идут через staged-commit. ⚠ Прежняя запись №18 +(«атомарны три из шести, среди них `bank-stop.json`») в этой части УСТАРЕЛА — если встретишь её в +доках, это она, а не твой результат. + +**Строка бэклога 240** («оплаченный стоп подписи может исчезнуть МОЛЧА») заведена оркестратором №20 +ЧТЕНИЕМ КОДА и честно помечена **PLAUSIBLE — сценарий не воспроизведён**. Её платформенную половину +берёт пак P12. Дёшево предъявишь что-то ЗА или ПРОТИВ — это ценнее чтения кода; дорого — не гонись, +скажи в obstacles. + +### 5.5 Что ты оставляешь платформе (это и есть снятие гейта 202/216) + +Платформа **сочинила сама** форму артефактов шва и никогда не видела настоящих. Каналов **ПЯТЬ**, а не +три: **манифест** (`manifest --json`) · **сайдкар банка** (`.bank.json`) · **причины флагов** · +**`events.jsonl`** (аутбокс схемы v15 — главный канал шва, который твой прогон и создаёт) · +**`export --json --pairs`** (единственный канал, несущий ТЕКСТ). + +**Чего ждёт другая сторона — читай в ЕЁ коде, не по памяти и не из этого промта** (D39.132): манифест — +`platform/internal/ingest/manifest.go`, сайдкар банка — `platform/internal/ingest/bank.go`, причины +флагов — `platform/internal/ingest/notes.go` (рукописный словарь причин → коды замечаний контракта). +Читать чужую зону можно, писать в неё нельзя. + +⚠ **Карта причин флагов — НЕ файл движка:** движок отдаёт причины значениями `flag_reason` в +`export`/`status`; это и есть строка бэклога 204. Сравнивай наблюдённые значения со словарём +`notes.go`. ⚠ **Расхождений может не быть вовсе** — «сверено, расхождений ноль, вот обе стороны +списком» полноценный результат, а не пустая работа. + +Каталог прогона остаётся на диске целиком; в отчёт положи ФОРМУ артефактов (структура, ключи, примеры +значений) — отчёт уедет в репозиторий, каталог нет. + +**Дёшево и сверх заказа, если останется время** (обе пробы $0, обе после платной части): назови в +отчёте путь к своей БД и её размерности (глав/юнитов/строк) — `PD-297` платформы ждёт корпусную книгу +для замера round-trip, и без этого следующая сессия пере-покупает прогон. + +### 5.6 $0-провокации ПОСЛЕ платной части — ЖЁСТКИЙ ПОРЯДОК + +⛔ **ГЛАВНОЕ ПРАВИЛО, и оно бьёт твою свободу выбирать порядок фаз (§4): ПЕРВАЯ ЖЕ правка исходника +закрывает платную часть НАВСЕГДА.** После неё ни `translate`, ни `redrive` по этой книге не +запускаются. Причина — строка бэклога **238**, подтверждённая обеими половинами: резюм-фаст-пас +требует совпадения И `content_hash`, И снапшота; правка исходника НА МЕСТЕ (исходник в снапшот не +входит) роняет фаст-пас, и `translate` **перекупает юниты БЕЗ предупреждения в смете**. Гейт +`--accept-rebill` этот путь не ловит по конструкции. Единственный видимый след — INFO-строка +`manifest: the stored manifest is stale (source or cut changed)`. + +Порядок проб — ровно такой: + +1. **`stale`** — правка исходника, **РАВНАЯ ПО БАЙТАМ** и внутри чанка (сдвинешь границы нарезки — + получишь `ghost` вместо `stale`, а в отчёте напишешь, что stale проверен) → `status --json` → + `build`. ⛔ Идёт ПЕРВОЙ: `staleUnits` под config drift возвращает «неизвестно» сразу, ничего не + считая, а любая правка конфига двигает снапшот ⇒ `drift=true` ⇒ ось `stale` для этой книги закрыта + НАВСЕГДА. +2. **Строка 238** — на тех же данных: `status --json` скажет, что пере-оплаты нет, а `build` скажет + `stale N`. **Расхождение двух $0-поверхностей и есть предъявление строки.** +3. **`--partial` / дыры** — с `--out` в отдельный путь (§5.1), и здесь же глазами найти русскую строку + маркера дыры — это второй шаг оси §1б. +4. **Второй `--verify-bank`** (§5.4). +5. **Второй `translate`** — строка бэклога **198**: юниты резолвятся бесплатно; посмотри, появились ли + ПОВТОРНЫЕ анонсы юнитов в `events.jsonl`. Ключ анонса — `unit::::`, без + метки нарезки; это первый настоящий аутбокс на схеме v15 и первая возможность увидеть механизм на + данных, а не в коде. +6. **ПОСЛЕДНЕЙ — строка 239:** снять edit-стадию в **КОПИИ** `pipeline.yaml` → `status --json` покажет + `config_drift=false` и черновики как полную книгу, а `export`/`build` на том же конфиге покажут + дрейф. ⛔ Эта проба двигает снапшот, поэтому она последняя. + +### 5.7 Что закрывается ДАРОМ — из того, что ты и так сохраняешь + +**Строки бэклога 154 и 157** гейчены именно этим прогоном и закрываются числами, которые уже печатаются: +из stderr терминолога выпиши `families` / `families_refused` / `families_held`; из `manifest --json` — +распределение размеров юнитов (минимум/медиана/максимум знаков) и `units_total` по главам. Это +единственный носитель замера, на который обе строки гейчены; без него они переезжают на следующий +платный прогон. + +## 6. Мандат самопроверки ИСПОЛНЕНИЕМ + +Самоотчёт «проверено» без исполнения регулярно оказывается ложным и здесь стоит ДЕНЕГ. + +**Тебе ЯВНО разрешены субагенты.** Модель задавай ИМЕНЕМ и знай, сколько их у тебя работает. +По умолчанию для трудного суждения — **Fable 5**; обычно хватает одного-двух, веер под предмет +выбираешь ты. + +1. **Записка-план ДО первой покупки** — что покупаешь, каким потолком защищено, чем докажешь; `N` для + обеих порций `--max-units` названы числом. Пишется в каталог прогона **и ДОСЛОВНО повторяется первой + секцией отчёта**: отчёт уезжает в репозиторий, каталог прогона нет. Записка, дожившая только до + конца сессии, приёмке не предъявляется. +2. **Сухой проход $0 ПЕРЕД покупкой:** `status` · `manifest` · `report` ключей не требуют. ⚠ Но + `status`/`manifest` на никогда-не-запускавшейся книге **СОЗДАЮТ и мигрируют БД**. ⚠ На ТВОЕЙ свежей + книге `tmctl migrate` напечатает `migrate: created at schema v16` (или `already at schema v16`); + **`backups/` НЕ появится** — restore point на новой БД не берётся. Строка `pre-migrate: backed up … + at schema v14` — контракт СУЩЕСТВУЮЩЕЙ книги, и её ты не увидишь. Хочешь увидеть глазами — сделай на + СВОЕЙ КОПИИ чужого стенда вне `books/`, и так и запиши. +3. **Деньги — двумя независимыми путями.** Оба печатает ОДНА $0-команда + `tmctl report --config book.yaml`: строка `Book ledger: committed=… reserved=…` считает + `SUM(spend.committed_usd)`, а `ROUTING/MONEY: … spend by model:` собирается из `checkpoints`. + Сойдутся — приводишь обе; разойдутся — это НАХОДКА, а не помеха. +4. **Активная проверка живости.** Раз в пару часов сверяй рост числа вызовов и трат по леджеру ДВУМЯ + чтениями. Зависшая задача от идущей неотличима по молчанию. +5. **Адверсариальный проход по СВОЕЙ ГОТОВОЙ работе — ПЕРЕД сдачей.** Не перечтение: субагент получает + твой отчёт, твои команды и мандат «найди, где она выдаёт непроверенное за проверенное». + **Артефакт — секция отчёта «Адверсариальный проход»:** какой моделью, сколько агентов, что каждому + дано на вход, и КАЖДАЯ находка дословно + твоя диспозиция (принято и исправлено / отклонено с + доводом). Ноль находок — тоже запись, но тогда назови, что именно субагент проверял и какими + командами. + Оси, на которых пак уязвим **по моему суждению** — приор, не потолок: клеймы о деньгах · клеймы о + кодах выхода, которых ты не видела · клейм «EPUB валиден» · утверждение, что артефакт совпал с + ожиданием платформы. Считаешь, что уязвимо другое (тождество нарезки, честность `complete`) — + замени ось с доводом и скажи об этом. +6. **Перед отчётом сверь КАЖДЫЙ клейм с результатом инструмента ЭТОЙ сессии.** +7. **Последний абзац отчёта — план или обещание? Сделай сейчас.** +8. **Интервальная сверка с заказом.** На каждой естественной границе (после сухого $0-прохода · после + первой порции · после edit-волны) прогоняй по осям §5 короткую сверку «что уже предъявлено / что ещё + нет», и хотя бы одну отдай субагенту с явными критериями §7. Длинная сессия дрейфует от заказа + незаметно для себя. + +⛔ **Тесты и гейты не подгонять под зелень.** Править или удалять тест/голден/гейт, чтобы он прошёл, — +НЕДОПУСТИМО. Несогласие с тестом — пинг, не правка. + +## 7. Записка-план и комплектность + +До первой покупки — записка-план (§6.1). Перед сдачей — механическая сверка отчёта против §5 по осям: +каждая ось получает вердикт **предъявлено / не предъявлено / опровергнуто**. Три исхода, не два: +«не проверено» ≠ «опровергнуто». + +## 8. «Заявление = команда» + +Каждое число и каждая категорика отчёта идут С КОМАНДОЙ, которой получены. Приёмка пере-ранит +выборочно. Клейм о рантайме без живого прогона = **PLAUSIBLE**, и так и помечается. Клейм без +ре-рана — «со слов сессии». + +## 9. Эхо-протокол старта + +**ДО первой правки и ДО первой покупки** — ≤10 строк: скоуп / инварианты / чего не делаешь. +Отправь первым действием по каналу (§12). Эхо служит и проверкой канала. + +## 10. Obstacle reporting + +Обязательная секция отчёта: **что НЕ удалось и что НЕ проверено**. Отсутствие инструмента, недостижимая +ось, непонятое поведение — сюда. Пустая секция при непустой работе читается как дефект отчёта. +Ты вправе сказать «этого делать не надо» с аргументом — это поощряется, а не штрафуется. + +## 11. Канал вопросов + +Конфликт промта с кодом или доками ⇒ **пинг, НЕ интерпретация**. Промт мог устареть между написанием и +исполнением; код первичен. Настоящую развилку неси быстро. +**Правь курс через оркестратора:** mid-flight правки доезжают ТОЛЬКО релеем — владелец пере-передаёт +правку отдельным сообщением, ты эхо-подтверждаешь, и она отдельным пунктом отчёта. + +## 12. Прямой канал + +Механизм — `CLAUDE.md` §«Связь между сессиями»; файл **`/tmp/textmachine-channel`**. Впиши туда СВОЙ +блок первым делом, дописывая в КОНЕЦ и не трогая чужие. Перед тем как писать кому-то — сверься с +`ListAgents`: файл переживает смерть сессии, `ListAgents` нет. +⚠ Дословно: **нужной роли нет, файла нет или имя не отвечает ⇒ КАНАЛА НЕТ, и это НОРМАЛЬНЫЙ случай: НЕ +опрашивай сессии подряд.** Вопрос — секцией в отчёт. + +--- + +## Приложение А. Известные грабли прежних холодных прогонов + +Промт на холодный прогон писался ДВАЖДЫ, оба отработаны (`docs/archive/prompts/`). Третий заход (работа +C промта `BACKEND_EFFORT_HANDLE`, 02.08) закрыт владельцем как неуспешный — и **прогон там не начался +вовсе**. Техническая причина: `DeepSeek-V4-Flash-0731` сменил веса под тем же слагом, а thinking у +вендора включён по умолчанию с эффортом high. +⇒ **Слаг живой ≠ модель та же.** Перед покупкой сверь ПОВЕДЕНИЕ модели, а не только доступность слага. + +`coldrun-b` заморожен ЛЕГИТИМНО: фаза C остановлена чекпойнтом по букве промта (критерий «0 пустых» +нарушен 8 из 9). Его собственная база ПУСТА — деньги «coldrun-b» потрачены в подпроектах `reprobe/*`. +Оба каталога — улика, read-only. + +## Приложение Б. Готовые команды (проверены на HEAD, но пере-проверь) + +``` +# бинарь из HEAD + его личность +cd /home/ubuntu/projects/textmachine-main/backend && go build -o <твой путь>/tmctl ./cmd/tmctl +sha256sum <твой путь>/tmctl + +# $0-глаголы, ключей не требуют, безопасны в любом порядке: +tmctl --config book.yaml +tmctl bank-apply --config book.yaml --decisions decisions.json [--dry-run] +tmctl seed-lint --seed glossary.yaml + +# ⛔ ПЛАТНЫЕ: +# translate — твой глагол, см. §1в +# redrive — этот пак его НЕ заказывает НИГДЕ. Не запускай: он и платит, и разрушительно +# сбрасывает флаговую телеметрию, и принимает голую форму без селектора. + +# полная стоимость из сырья — ⛔ ВСЕГДА mode=ro (§2) +sqlite3 'file:?mode=ro' "SELECT printf('%.6f',COALESCE(SUM(committed_usd),0)), + printf('%.6f',COALESCE(SUM(reserved_usd),0)) FROM spend WHERE book_id='';" +``` diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 0498fa05..aa68f122 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -1,6 +1,6 @@ # Журнал прогресса -> **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-08-30, голова D39.178 — КУРС: движок и платформа до «работает и отдаёт результат», фронт ЗАМОРОЖЕН и P7 его НЕ размораживает (D39.147). **ОЧЕРЕДЬ №21** (единственный носитель — здесь; роль передана 31.08, №20 закрыт нотой передачи D39.177): **(1) РАЗРЫВ ЦИКЛА: книга не отдаётся файлом — ни одной стороной.** Канон обещает `createExport`/`getExport`, платформа их НЕ монтирует (смонтировано 15 операций из 20; нет также `getRun`, `updateBook`, `deleteBook`), а движок писателя не имеет вовсе — `tmctl export` пишет только в stdout (JSON `tm-export-v1` либо текст с аудит-баннерами). ⚠ Прежняя редакция обосновывала это грепом `epub|fb2|docx` («21 хит, все на стороне ингеста») — довод СНЯТ 30.08: он и был хрупким (сырой счёт хитов), и стал ложным после лендинга. Долг числится с P8 (`platform/BACKLOG.md` П-17), с тех пор легло пять паков и ни один его не взял. **Слово «отдаёт результат» сегодня ложно по букве** — ⚠ 30.08: ДВИЖКОВАЯ половина ЗАКРЫТА паком «писатель книги» (D39.175, `8adcb86`): `tmctl build` отдаёт EPUB 3 + чистый txt с шестью состояниями честности; открытой остаётся дверь платформы (п.4) · **(2) холодный прогон движка на текущем HEAD** — гейт строк 202/216, слово владельца 20.08: с 27.08 легло ~15 нот (шов, стоп банка флажком, схема v16, класс отказа 15, объёмный потолок, гейт схемы) и НИ ОДНА не проверена деньгами; ≈$0.05. ⚠ **ГЕЙТ КЛЮЧЕЙ СНЯТ словом владельца 30.08** (D39.176 п.6): движковые ключи починены, движок берёт конвенционный `.env` рядом с `book.yaml` — прогон разблокирован, ждёт только промта оркестратора. Для п.3 нужен ДРУГОЙ файл (путь платформы `TM_PLATFORM_ENGINE_KEYS_PATH`) — вопрос владельцу открыт · **(3) живой сквозной прогон через API платформы** — форма манифеста, сайдкара банка и карта причин флагов сочинены платформой САМОСТОЯТЕЛЬНО и первыми настоящими данными движка не проверялись · **(4) дверь выдачи** — строго ПОСЛЕ (3) (⚠ слово владельца 30.08: дверь ВСЕГДА строит файл — отказ юзеру не отдаётся, неполная книга уходит с пометкой, т.е. дверь зовёт `tmctl build --partial`; отказ по умолчанию остаётся операторской ручкой CLI; D29.1(б) «fail-closed» читать как «никогда молча» — честность несут пометки) · **(5) проводка `--max-units`** — снятие гейта, а не стройка: механизм в движке стоит (D39.170), живое основание гейта — `PD-422` · **(6) разморозка фронта** — ПОСЛЕ (4): зеркало отстало на ШЕСТЬ миноров (`frontend/docs/api-contract/openapi.yaml` — `0.2.3` против канона `0.8.0`, и первый из них — ЛОМАЮЩИЙ 0.3.0), 16 операций против 20; отставание ратифицировано D39.142 п.5 на время фриза и две ручки разъехались, размораживать раньше значит перегенерировать типы дважды. ⚠ **ДВА ЗАГЕЙЧЕННЫХ ТРИГГЕРА НАЖАТЫ И НЕ ОТРАБОТАНЫ** (`13-tech-debt-anchors.md` §Б-108): `go.work` — у платформы свой модуль есть, файла в корне НЕТ; вендор-сверка маппинга эффорта `deepseek-v4-pro` — сработала 13.08, носители противоречат, закрывается ТОЛЬКО вендор-докой. ⚠ **ЧТО В ПРЕЖНЕЙ ОЧЕРЕДИ БЫЛО НЕВЕРНО, проверено деревом 30.08:** пункт про `sqlc` отдельной сессией ИСПОЛНЕН (D39.172, `63fcee5`); «работа полигона в дереве НЕЗАКОММИЧЕНА, читать `git status -- eval/`» ОПРОВЕРГНУТО — она лежит коммитом `28c2d0f` на ветке `polygon`, запушена, в `main` не влита, а названная команда возвращает пустоту и уводит в вывод «работы нет»; «развязка git с origin» ОПРОВЕРГНУТА — `main` и `origin/main` на одном коммите. +> **⟶ ТЕКУЩЕЕ СОСТОЯНИЕ** (на 2026-08-31, голова D39.179 — КУРС: движок и платформа до «работает и отдаёт результат», фронт ЗАМОРОЖЕН и P7 его НЕ размораживает (D39.147). **ОЧЕРЕДЬ №21** (единственный носитель — здесь; роль передана 31.08, №20 закрыт нотой передачи D39.177): **(1) РАЗРЫВ ЦИКЛА: книга не отдаётся файлом — ни одной стороной.** Канон обещает `createExport`/`getExport`, платформа их НЕ монтирует (смонтировано 15 операций из 20; нет также `getRun`, `updateBook`, `deleteBook`), а движок писателя не имеет вовсе — `tmctl export` пишет только в stdout (JSON `tm-export-v1` либо текст с аудит-баннерами). ⚠ Прежняя редакция обосновывала это грепом `epub|fb2|docx` («21 хит, все на стороне ингеста») — довод СНЯТ 30.08: он и был хрупким (сырой счёт хитов), и стал ложным после лендинга. Долг числится с P8 (`platform/BACKLOG.md` П-17), с тех пор легло пять паков и ни один его не взял. **Слово «отдаёт результат» сегодня ложно по букве** — ⚠ 30.08: ДВИЖКОВАЯ половина ЗАКРЫТА паком «писатель книги» (D39.175, `8adcb86`): `tmctl build` отдаёт EPUB 3 + чистый txt с шестью состояниями честности; открытой остаётся дверь платформы (п.4) · **(2) холодный прогон движка на текущем HEAD — ПРОМТ ВЫДАН 31.08** (`BACKEND_COLDRUN_V16_SESSION_PROMPT.md`, D39.179), прогон НЕ НАЧАТ. Гейт строк 16/154/157/160/198/202/216, слово владельца 20.08: с 27.08 легло ~15 нот (шов, стоп банка флажком, схема v16, класс отказа 15, объёмный потолок, гейт схемы) и НИ ОДНА не проверена деньгами. ⚠ **Цена: слово владельца 31.08 — ≈$0.60, потолок $0.80, ДЕСЯТЬ глав ПОЛНОЙ цепью включая редакторскую волну.** Прежняя «≈$0.05» СНЯТА как протухшая: пере-пин DeepSeek 16.08 даёт множитель 4.47 на том же заходе (D39.179 п.1). ⚠ **ГЕЙТ КЛЮЧЕЙ СНЯТ словом владельца 30.08** (D39.176 п.6), но конвенционного `.env` НЕТ НИ У ОДНОЙ книги корпуса — прогон обязан передавать `--keys-file` абсолютным путём; заполнены только `projects/textmachine/backend/.env` и `.../eval/.env`. Для п.3 нужен ДРУГОЙ файл (путь платформы `TM_PLATFORM_ENGINE_KEYS_PATH`) — вопрос владельцу открыт · **(3) живой сквозной прогон через API платформы** — форма манифеста, сайдкара банка и карта причин флагов сочинены платформой САМОСТОЯТЕЛЬНО и первыми настоящими данными движка не проверялись · **(4) дверь выдачи** — строго ПОСЛЕ (3) (⚠ слово владельца 30.08: дверь ВСЕГДА строит файл — отказ юзеру не отдаётся, неполная книга уходит с пометкой, т.е. дверь зовёт `tmctl build --partial`; отказ по умолчанию остаётся операторской ручкой CLI; D29.1(б) «fail-closed» читать как «никогда молча» — честность несут пометки) · **(5) проводка `--max-units`** — снятие гейта, а не стройка: механизм в движке стоит (D39.170), живое основание гейта — `PD-422` · **(6) разморозка фронта** — ПОСЛЕ (4): зеркало отстало на ШЕСТЬ миноров (`frontend/docs/api-contract/openapi.yaml` — `0.2.3` против канона `0.8.0`, и первый из них — ЛОМАЮЩИЙ 0.3.0), 16 операций против 20; отставание ратифицировано D39.142 п.5 на время фриза и две ручки разъехались, размораживать раньше значит перегенерировать типы дважды. ⚠ **ДВА ЗАГЕЙЧЕННЫХ ТРИГГЕРА НАЖАТЫ И НЕ ОТРАБОТАНЫ** (`13-tech-debt-anchors.md` §Б-108): `go.work` — у платформы свой модуль есть, файла в корне НЕТ; вендор-сверка маппинга эффорта `deepseek-v4-pro` — сработала 13.08, носители противоречат, закрывается ТОЛЬКО вендор-докой. ⚠ **ЧТО В ПРЕЖНЕЙ ОЧЕРЕДИ БЫЛО НЕВЕРНО, проверено деревом 30.08:** пункт про `sqlc` отдельной сессией ИСПОЛНЕН (D39.172, `63fcee5`); «работа полигона в дереве НЕЗАКОММИЧЕНА, читать `git status -- eval/`» ОПРОВЕРГНУТО — она лежит коммитом `28c2d0f` на ветке `polygon`, запушена, в `main` не влита, а названная команда возвращает пустоту и уводит в вывод «работы нет»; «развязка git с origin» ОПРОВЕРГНУТА — `main` и `origin/main` на одном коммите. > - ⚠ **ОБЯЗАТЕЛЬСТВА ЛЕНДИНГА P9 ИСПОЛНЕНЫ — но НЕ лендингом** (снято оркестратором №20 30.08, сверено статусами регистра): `PD-400`.2 → `accepted-risk`, `PD-401` и `PD-166` → `fixed` перевёл аудит доков **D39.167** (`373ccc7`, 28.08), чья находка №1 и говорит: «регистр расходился с МОИМИ ЖЕ актами лендинга — четыре строки». Прежние два буллета висели здесь двое суток после исполнения, и проверочная команда одного из них сама возвращала `fixed`. **Урок в норму приёмки: акт лендинга, объявивший закрытие строки, обязан быть сверен статусом В ТОТ ЖЕ заход.** > - ⚠ **ЛЕНТА КОНТРАКТА за 27–28.08, свёрнуто аудитом 28.08 (D39.167):** **0.5.0** — D39.161 (снос отменённой пер-термной модели подписи + дверь правок банка) · **0.6.0** — D39.162/D39.163 (сквозная полоса прогресса; `Progress.stage` ВТОРЫМ и последним исключением границы) · **0.7.0** — D39.166 (покупка пере-прохода членом `RunRequest.re_pass`). Прожитые дампы приёмки P9 и P10 вынесены в `archive/PROGRESS-2026-08-backend.md` и в тела нот; здесь остаётся только лента версий. > - ⚠ **27.08: ВЫДАН ПРОМТ КОНТРАКТНОГО МИНОРА** (`CONTRACT_MINOR_SESSION_PROMPT.md`, пункт 2б очереди D39.156). **ВОПРОС ЗАКРЫТ СЛОВОМ ВЛАДЕЛЬЦА 27.08 (D39.160):** сквозная полоса прогресса (строка **200**) из минора ИСКЛЮЧЕНА и едет с паком (2в). Причина механическая: гейт платформы требует, чтобы объявленная деплоем версия равнялась канону, поэтому канон и деплой обязаны совпасть В МОМЕНТ ЛЕНДИНГА; снос двери этому не мешает (платформа свою половину снесла 22.08), а смена семантики прогресса мешает — сервер её не считает и не будет до пака (2в), и `0.5.0` у старого прогресса был бы ровно той ложью, против которой гейт стоит. Прежнее «одним куском» покупалось тем, что иначе фронт перегенерируется дважды, — но фронт ЗАМОРОЖЕН и перегенерируется один раз независимо от числа миноров. Промт ВЫДАН: прошёл оба рубежа роли, девять находок опровергателя применены. @@ -202,7 +202,7 @@ | 240 | **Оплаченный стоп подписи банка может исчезнуть МОЛЧА — единственная находка ревью P9, прожившая двое суток БЕЗ носителя** (Д3, `platform/docs/platform-PROGRESS.md`, греп `Потеря/порча маркера выхода` — ⚠ без номера строки НАМЕРЕННО: журнал живой зоны двигается её сессиями, и номер тут протухает за часы; заведена оркестратором №20 30.08 при ревизии P9). Рестарт/ретрай проходит границу банка НАСКВОЗЬ: память предъявления (`bank_stop_presented`, схема v16) покрывает карту, движок продолжает одним WARN себе в журнал, а платформенный гард `LiftBankStop` (греп `LiftBankStop` по `platform/internal/runs/reconcile.go` и `platform/internal/pgstore/runs.go` — ⚠ без номеров строк: файлы правит живая сессия зоны) писан против движка ДО памяти v16 и этот путь не держит. Движковую половину бэкенд-сессия ОТКЛОНИЛА правом §9 («символ живёт в `platform/`, движковая половина здорова») — диспозиция принята, но платформенная половина не получила ни строки регистра, ни строки бэклога. ⚠ Узел ТОТ ЖЕ, что у снятия обхода `--verify-bank` (пинг №21): `LiftBankStop` + колонка `bank_released`, и пак P12 §3.7 их уже трогает — закрывать ОДНОЙ работой, не двумя | платформа (+бэкенд на сверку) | скоро (с §3.7 пака P12) | снять гард вместе с обходом либо доказать, что память v16 делает его недостижимым — с живой пробой на стенде | ревизия P9 оркестратором №20, 30.08 | | 241 | **Канон противоречит сам себе на ДОЧИТАННОЙ книге, и принятая правка банка умирает молча** (названо телом D39.165 §3 28.08, носителя не получило; заведено оркестратором №20 30.08 по находке сессии P12). Две фразы: правка банка «takes effect on the NEXT run» (`docs/architecture/14-api-contract/openapi.yaml:504`=`takes effect on the NEXT run`) против «finished work is not bought twice» + подъём потолка только у ПРИОСТАНОВЛЕННОЙ книги (`:590`=`finished work is not bought twice`). У дочитанной книги следующего прогона купить нечем: пользователь правит банк, получает квитанцию об успехе — и правка не доезжает ни до одной строки никогда. ⚠ Смежное лечение уже существует и не связано с этой дырой: пере-проход ПОКУПАЕТСЯ членом `RunRequest.re_pass` (канон 0.7.0, D39.166) — то есть механизм есть, а фраза канона его не признаёт для дочитанной книги. Решать формой: либо канон признаёт пере-проход входом для дочитанной книги, либо дверь правок отказывает такой книге ЧЕСТНО вместо ложной квитанции | контракт (+платформа) | скоро (следующее касание контракта) | контрактный минор + пин на дочитанной книге | D39.165 §3, эррата 30.08-б, находка сессии P12 | | 242 | **Реестр D-нот: колонки «статус» и «тело» разъехались на 74 строках из 224** (замер аудита доков 30.08). У свежих нот в «статусе» стоит заметка «ЖИВОЕ: …», а слово `жив` уехало в «тело»; шапка таблицы описывает другой порядок. Гейт `counts.py --check` этот класс НЕ ловит по устройству (сверяет полноту номеров, не форму ячеек). Лечение — не ручная перекладка (дороже пользы и легко внести новую ложь), а машинное: правило формы в `counts.py` + разовый прогон приведения. Пока не сделано — в легенде реестра стоит правило чтения (испр. 30.08) | оркестратор | когда-нибудь | правило в `counts.py` + разовое приведение | аудит доков 30.08 | -| 243 | **Остаток аудита доков 30–31.08: 19 удержанных находок НЕ применены** (аудит — 5 линз + верификатор на каждую, 57 находок, 55 удержаны, 36 применены сменой №20). Перечень: **arch-3** (major: `15-money-path.md` продаёт себя как «каждое утверждение с указателем на первичный носитель», а из 13 якорей 9 ведут не в тот код; починены 2, остальные — нет) · **arch-5** (правило языка знает две корзины, `reader.txt` — третья) · **arch-7** (§2 закона шва: дисциплина флока не описывает новый глагол-писатель) · **arch-9** (реестр загейченных триггеров не знает нажатых 30.08) · **arch-10** (`16-events-emitter.md`: словарь и `StreamVersion` разошлись с кодом обеих зон) · **arch-11** (`10-prompt-architecture.md`: описывает несуществующий с пака-15 ключ `prompts:`) · **arch-12** (шапка `09-target` сама зовёт себя неполной — строку под таблицей слоёв надо внести) · **contract-8** (эррата 15.08-в в шапке D-лога указывает на промт, уехавший в архив) · **contract-10** (`Export.failure_code` «becomes an enum with the first built format» — формат построен, условие наступило) · **progress-6/7/8/10/11/12/13/14/15** (мелочь CURRENT-STATE и строк бэклога: протухшие «запушена»/«строка 200»/якорь `miner_emit.go`/дубль 238 внутри 160 и пр.). ⚠ Отчёт аудита жил в песочнице сессии №20 и умирает с ней — здесь ЕДИНСТВЕННЫЙ носитель списка; воспроизведение = повторный аудит (5 агентов, ~2 часа) | оркестратор | скоро | пройти списком; major (arch-3) первым — док про деньги врёт указателями | аудит доков 30–31.08, сдача смены №20 | +| 243 | **Остаток аудита доков 30–31.08: 19 удержанных находок НЕ применены** (аудит — 5 линз + верификатор на каждую, 57 находок, 55 удержаны, 36 применены сменой №20). Перечень: ~~**arch-3**~~ **ЗАКРЫТ 31.08 (D39.179 п.5)** — и был вдвое больше заявленного: не 13 якорей, а **51** (34 уехали, 1 мёртв, 16 верны), а дороже адресов оказались ЛОЖНЫЕ КЛЕЙМЫ (цитата контракта, которой нет; потерянный третий член `Usage`; живой К-13 при закрытом D39.132; метод `Settle`, которого нет). Чинилась ФОРМА, не номера: сверяемых гейтом по содержимому стало 89→127 · **arch-5** (правило языка знает две корзины, `reader.txt` — третья) · **arch-7** (§2 закона шва: дисциплина флока не описывает новый глагол-писатель) · **arch-9** (реестр загейченных триггеров не знает нажатых 30.08) · **arch-10** (`16-events-emitter.md`: словарь и `StreamVersion` разошлись с кодом обеих зон) · **arch-11** (`10-prompt-architecture.md`: описывает несуществующий с пака-15 ключ `prompts:`) · **arch-12** (шапка `09-target` сама зовёт себя неполной — строку под таблицей слоёв надо внести) · **contract-8** (эррата 15.08-в в шапке D-лога указывает на промт, уехавший в архив) · **contract-10** (`Export.failure_code` «becomes an enum with the first built format» — формат построен, условие наступило) · **progress-6/7/8/10/11/12/13/14/15** (мелочь CURRENT-STATE и строк бэклога: протухшие «запушена»/«строка 200»/якорь `miner_emit.go`/дубль 238 внутри 160 и пр.). ⚠ Отчёт аудита жил в песочнице сессии №20 и умирает с ней — здесь ЕДИНСТВЕННЫЙ носитель списка; воспроизведение = повторный аудит (5 агентов, ~2 часа) | оркестратор | скоро | пройти списком; major (arch-3) первым — док про деньги врёт указателями | аудит доков 30–31.08, сдача смены №20 | | 217 | **Скрипты полигона захардкодили АБСОЛЮТНЫЕ пути двух корней и не работают на этой машине вовсе.** ДВА РАЗНЫХ слома. **Корень репо** — смена пользователя: литерал в 121 файле, из них 104 дословно `REPO = Path("/home/ubuntu/projects/textmachine")`, портабельных форм всего 5. **Корень книг** — оба события порознь: литерал `/home/ubuntu/books` 86 файлов (пользователь), портабельный `Path.home()/"books"` 89 файлов (переезд каталога; на прежних машинах работал). Объединение 173 файла. Пользователь теперь `ubuntu-26`, книги переехали в `<репозиторий>/books` ⇒ ломается и то и другое, включая ЖИВОЙ гейт фазы Д `eval/conformance.py:48`=`REPO = Path("/home/ubuntu/projects/textmachine")`. Живых носителей 26 по корню книг (среди них портабельная форма преобладает 20:6) плюс корневые гейты и сборщики корпусов; остальное — скрипты закрытых эксп-12–16, их владелец велел ВЫБРОСИТЬ, а не чинить (D39.157 п.4а). Форма правки — за зоной; приор оркестратора: один модуль путей, корень резолвится МАРКЕРОМ вверх по дереву (движок так и делает — `backend/internal/miner/miner_parity_test.go:29`=`derived from the repository MARKER`), книги от него, обе ручки перекрываются env | полигон | скоро (блокирует любой прогон зоны) | пак путей полигона | переезд машины 24.08, №19 | | 219 | **Line-якоря в тела D-лога обречены съезжать, и механизм именно в дисциплине эррат.** Эрраты вписываются в КАРТУ ШАПКИ (append-only, D23.3), то есть в начало файла, — значит каждая эррата сдвигает номера строк ВСЕХ тел ниже, и любой якорь вида `05-decisions-log.md:NNN` умирает молча. ⚠ Замерено на себе 27.08: три эрраты за сессию убили якоря в `17-seam-inbound-law.md:120` и `research/25-seam-cold-review.md:5` (оба целили в `:232`, тело уехало на `:239`). Лечение — не пере-нацеливание (оно повторится через эррату), а СМЕНА ФОРМЫ якоря: в тела D-лога целиться номером ноты (`^## D39.106`), который стабилен навсегда, а не строкой. ⚠ Строка ОБЪЯВЛЕНА в CURRENT-STATE 24.08 и до 27.08 в таблице НЕ СУЩЕСТВОВАЛА — очередь три дня ссылалась на носитель, которого нет; поймано свипом якорей при выдаче контрактного минора | оркестратор | скоро (растёт с каждой эрратой) | `--lint` учит форму `файл:^## D<номер>`, живые якоря в D-лог переводятся на неё | ревью доков 24.08, механизм и отсутствие носителя — 27.08, D39.160 | | 220 | **Репо-широкий гейт доков бутстрапится пакетным менеджером ОДНОЙ зоны, и та заморожена.** Диспетчер `.git/hooks/pre-commit` зоно-нейтрален по построению (`for hook in */scripts/githooks/pre-commit`, `#!/bin/sh`, node не нужен) и судит `CLAUDE.md` + `docs/**` + `platform/docs/**` + `frontend/docs/**` (`docs/scripts/counts.py:500`=`ROOT / "CLAUDE.md"`), но единственный его УСТАНОВЩИК — `frontend/scripts/githooks/install.mjs`, вызываемый ключом `prepare` из `frontend/package.json`. Охват и бутстрап не совпадают ⇒ инструмент решает свою задачу не в полной мере. Проверено исполнением 24.08: на этой машине `.git/hooks/` содержал только `*.sample`, гейт МОЛЧАЛ, ничего не проверив, и поймал первые четыре дефекта только после ручной установки; сессия бэкенда или доков `npm install` не делает никогда. Приор: зоно-нейтральный установщик в `docs/scripts/githooks/` как ЕДИНСТВЕННЫЙ писатель диспетчера, `prepare` фронта зовёт его — фронтовая половина выписана пингом №19 в зонный журнал фронта. ⚠ Ручная установка — временная мера, пока строка открыта; записывать её как порядок работы ЗАПРЕЩЕНО (это ровно тот обход, цену которого называет шапка `CLAUDE.md`) | доки + фронт | скоро | зоно-нейтральный бутстрап + правка `prepare` при разморозке | аудит переезда 24.08, №19 | diff --git a/docs/README.md b/docs/README.md index a0f69f35..1fddb7a7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,7 +22,7 @@ | Роль | Активный промт | Статус | |---|---|---| | Оркестратор | [ORCHESTRATOR_SESSION_PROMPT.md](ORCHESTRATOR_SESSION_PROMPT.md) | роль и нормы; счётчик роли — CURRENT-STATE | - | Бэкенд | активного НЕТ | пак «писатель книги» **ПРИНЯТ И ЗАЛЕНДЖЕН 30.08** (**D39.175**, `8adcb86`): `tmctl build` — EPUB 3 + чистый txt из `BookExport`, шесть состояний честности, отказ exit 16 / `--partial`, пути в `StatusArtifacts.book_files`. Слово владельца: drift — НЕ дыра файла, дверь выдачи обязана сверять `BuildReport`. Приёмка: 4 линзы + верификатор на каждую находку, два дофикса до лендинга. Промт отработан — `archive/prompts/`. Открыто строками **238/239**; вопросы владельцу (лестница D25.1 · лейбл К-3 · RTL) — CURRENT-STATE | + | Бэкенд | [BACKEND_COLDRUN_V16_SESSION_PROMPT.md](BACKEND_COLDRUN_V16_SESSION_PROMPT.md) | **ВЫДАН 31.08** (**D39.179**): холодный прогон движка на схеме v16 — ПЕРВЫЙ ПЛАТНЫЙ сквозной прогон на текущем HEAD. Санкция владельца: ≈$0.60, потолок $0.80, десять глав ПОЛНОЙ цепью включая редакторскую волну; прежняя «≈$0.05» снята как протухшая. Гейт строк **16·154·157·160·198·202·216**. Оба рубежа пройдены: механическая сверка 12 блоков + опровергатель в четыре линзы — **53 находки, 8 блокеров**, все применены. Прежний пак «писатель книги» ПРИНЯТ И ЗАЛЕНДЖЕН 30.08 (**D39.175**, `8adcb86`): `tmctl build` — EPUB 3 + чистый txt, шесть состояний честности, отказ exit 16 / `--partial`. Открыто строками **238/239**; вопросы владельцу (лестница D25.1 · лейбл К-3 · RTL) — CURRENT-STATE | | Платформа | [PLATFORM_P12_SESSION_PROMPT.md](PLATFORM_P12_SESSION_PROMPT.md) | **ВЫДАН 30.08** сессии `textmachine-main-b5` (эхо принято; имя не переживает рестарт — адрес сверять `ListAgents`). Пак P12 «долги под ногами»: 7 открытых major (PD-425 деньги · PD-402/405 ложь экрана · PD-403/404 повторная продажа глав — решение владельца D39.165 §2 релеем · PD-424 замороженный холд) + снятие обхода `--verify-bank` (обязательство D39.158) + PD-369/431/432 + гигиена регистра. Оба рубежа пройдены: опровергатель (3 линзы + верификаторы) дал **21 удержанную находку**, все применены. Прежний пак `sqlc` — D39.172, промт в архиве: 40 запросов на типизированный слой, пин 1.31.1, `sqlc diff` в `make check`. ⚠ Решение принято НЕ доводом покрытия (3 из 42), а шестью ВЫЖИВШИМИ мутациями: `sqlgate` не видит Go-сторону вызова. Промт отработан — `archive/prompts/`. Открыто строками: `Touch` без проверки затронутых строк · `observe.go` неконвертируем | | Полигон | [POLYGON_EXP2223_REDO_SESSION_PROMPT.md](POLYGON_EXP2223_REDO_SESSION_PROMPT.md) (отложенный — [POLYGON_PACKAGE4_SESSION_PROMPT.md](POLYGON_PACKAGE4_SESSION_PROMPT.md), строка 85) | фаза Д ИДЁТ; ⚠ живой носитель курса — в `eval/dovodka/`, какой именно называет зона (⚠ [POLYGON_PHASE_D_HANDOFF.md](POLYGON_PHASE_D_HANDOFF.md) — перекрытый снимок, читать не как курс) | | Фронт | активного НЕТ | **ЗОНА ЗАМОРОЖЕНА** (D39.136 п.2 + D39.147: разморозка отдельным словом владельца, не привязана к P7); перечень первого касания — в зонном журнале | diff --git a/docs/architecture/05-decisions-index.md b/docs/architecture/05-decisions-index.md index 5258c13a..e015cf37 100644 --- a/docs/architecture/05-decisions-index.md +++ b/docs/architecture/05-decisions-index.md @@ -1,4 +1,4 @@ -# Реестр D-нот — карта актуальности v2 (D1–D39.178; +# Реестр D-нот — карта актуальности v2 (D1–D39.179; > ⚠ **СЛАБОЕ МЕСТО, КОТОРОЕ БЫЛО ЗДЕСЬ (вписано 22.08, ЗАКРЫТО 24.08 — D39.157 п.6).** Колонка ТЕЛА > у нот D39.107…D39.123 говорила «жив», хотя тела уехали в слайс подрезкой D39.139; семнадцать строк @@ -240,3 +240,4 @@ | D39.176 | 30.08 | **Пакет слов владельца по платформе**: продуктовых КВОТ НЕТ (живём на покупке API, бонусы из админки — `П-12`/`PD-175` сняты как продуктовая развилка, техническая гигиена остаётся инженерной) · продажа ПЛАНИРУЕТСЯ, но не в бете (`П-7` отложен, не отменён) · после пополнения баланса прогон возобновляется ЯВНЫМ действием (пункт снят с листа) · фразы пользователя ДЕЛЕГИРОВАНЫ проекту с требованием ИНТЕРНАЦИОНАЛЬНОСТИ — не литерал, а данные по коду причины и локали (гейт `PD-246`/строка 204). Ключи: движок берёт `--keys-file` ИЛИ конвенционный `.env`; холодный прогон разблокирован, живой через API — нет (платформа читает свой путь). | жив | ЖИВОЕ: на владельце остался ОДИН платформенный пункт — `PD-421` (держит ли открытый поток сессию живой), переформулирован | платформа продукт владелец квоты фразы | | D39.177 | 31.08 | **Передача роли оркестратора №20 → №21.** Смена приняла пак «писатель книги» (D39.175) и закрыла продуктовый лист платформы (D39.176), выдала промт P12 и ратифицировала минор 0.9.0 по пингу сессии. Три урока: опровергатель промта окупился в третий и четвёртый раз (62 находки; самая дорогая — «копия книги» через симлинк мигрировала бы ОРИГИНАЛ) · долг без носителя переживает приёмки (две находки ревью P9 прожили двое суток без строки) · якорь по номеру в файл живой сессии заведомо мёртв. Четыре ошибки названы; ТРИ из них поймал ВЛАДЕЛЕЦ вопросами «ты уверен / ты дочитал / почему не закрыл». Аудит всех живых доков: 57 находок, 55 применены. | жив | ЖИВОЕ: очередь №21 в CURRENT-STATE; пять обязательств переходят; незакоммиченное дерево пака P12 — норма, лендить при приёмке | передача процесс нормы аудит | | D39.178 | 30.08 (записано 31.08) | **Слово владельца о двери выдачи**: дверь `createExport` ВСЕГДА строит файл, отказ пользователю не отдаётся — неполная книга уходит с пометкой (`tmctl build --partial`), отказ exit 16 остаётся операторской ручкой CLI; D29.1(б) fail-closed читать как «никогда МОЛЧА»; дверь сверяет `BuildReport` — предупреждение оператору, не читателю. Ратификация жила в CURRENT-STATE без грепаемого носителя — пробел закрыт аудитом доков. | жив | ЖИВОЕ: лестница D25.1 (ступень копии без пометки) не решена, ждёт владельца перед дверью | контракт продукт выдача | +| D39.179 | 31.08 | **Слово владельца о цене холодного прогона**: ≈$0.60, потолок $0.80, десять глав ПОЛНОЙ цепью включая редакторскую волну; прежняя санкция «≈$0.05» снята как протухшая (пере-пин DeepSeek, множитель 4.47). Промт `BACKEND_COLDRUN_V16_SESSION_PROMPT.md` выдан — 53 находки двух рубежей применены. Норма формы указателей заострена: стабильна не ЗОНА, а ЧИСТЫЙ ФАЙЛ. `arch-3` закрыт: 51 якорь пере-проверен, 34 уехали, сверяемых по содержимому 89→127 | жив | ЖИВОЕ: прогон не начат; строки 16·154·157·160·198·202·216 гейчены им | деньги прогон доки якоря | diff --git a/docs/architecture/05-decisions-log.md b/docs/architecture/05-decisions-log.md index df4ead20..aa0e99dd 100644 --- a/docs/architecture/05-decisions-log.md +++ b/docs/architecture/05-decisions-log.md @@ -1,4 +1,4 @@ -# Журнал решений оркестратора — контракт D1–D39.178 (живой файл: карта · эрраты · живые тела · голова D39.124+ (подрезка D39.139); тела закрытых эр — в слайсах `docs/archive/architecture/`, указатель ниже; реестр всех нот — `05-decisions-index.md`) +# Журнал решений оркестратора — контракт D1–D39.179 (живой файл: карта · эрраты · живые тела · голова D39.124+ (подрезка D39.139); тела закрытых эр — в слайсах `docs/archive/architecture/`, указатель ниже; реестр всех нот — `05-decisions-index.md`) > **⟶ КАРТА АКТУАЛЬНОСТИ (ревизия D31, продлена до D38.2 [12.07]; исторические записи ниже НЕ переписываются — дисциплина D23.3).** Работая с контрактом (греп номера: живой файл → слайсы, целиком НЕ читать — D39.125), держи под рукой, что чем перекрыто: > ⚠ **Эррата 09.08 (D39.125):** D39.111 п.1 предписывал промту S3 «максимум = баланс МИНУС открытые холды» — формула ОШИБОЧНА (вычитание дважды), исправлена D39.115 п.2(а): максимум = Balance КАК ЕСТЬ; тело D39.111 живёт ниже в этом файле (голова D39.106+). @@ -1661,3 +1661,15 @@ head-1 это семнадцать бюджетируемых операций, 2. **D29.1(б) «reader-facing выдача fail-closed» читать как «никогда МОЛЧА»,** а не «никогда не отдавать»: честность несут пометка на титуле и маркер на месте каждой дыры (D39.175), а не отказ. Молчаливый полный файл на книге с дырой по-прежнему запрещён. 3. **Дверь дополнительно сверяет `BuildReport`** (`config_drift`/`stale_unknown`) — это предупреждение ОПЕРАТОРУ и интерфейсу, не читателю (D39.175 п.2). 4. ⚠ **Граница осталась:** лестница D25.1 — какая ступень даёт читателю копию БЕЗ пометки и что печатать на титуле/в имени файла — НЕ решена и ждёт владельца между этим паком и дверью. Пока её нет, дверь строит по пп. 1–3. + +## D39.179 — СЛОВО ВЛАДЕЛЬЦА 31.08 О ЦЕНЕ ХОЛОДНОГО ПРОГОНА + ВЫДАН ПРОМТ; норма формы указателей заострена (оркестратор №21). ✅ + +1. **Санкция владельца 31.08: ≈$0.60, потолок $0.80, ДЕСЯТЬ глав ПОЛНОЙ ЦЕПЬЮ включая редакторскую волну.** Из трёх предложенных вариантов владелец выбрал самый полный. **Прежняя цифра очереди «≈$0.05» СНЯТА как протухшая:** DeepSeek пере-пинен коммитом 15.08 (`76049bb`, D39.137) под цены, вступившие в силу 16.08 16:00 UTC; заход `bank-low` ($0.045095 в июльских деньгах) по ним стоит $0.2017 — множитель 4.47, пере-считан из его же токенов. + +2. **Промт ВЫДАН** — `BACKEND_COLDRUN_V16_SESSION_PROMPT.md`. Оба рубежа пройдены: механическая сверка 12 блоков + опровергатель в четыре линзы дали **53 находки, 8 блокеров**, все применены. Механика — в самом промте, здесь не дублируется. + +3. ⚠ **Эррата к п.1, названная перед владельцем:** санкция получена под формулировкой «редакторского вызова нет ни одного за всю историю проекта» — она ЛОЖНА и воспроизводила абсолют, уже исправленный №19 в строке бэклога 16. Верно: `judge` не вызывался НИ РАЗУ нигде, а редакторская волна гонялась и оплачена на 19 стендах; НЕ мерена **полная цепь ОДНИМ заходом на холодном старте** — у `coldrun-a` и всей семьи `coldrun-b` ноль редакторских вызовов. Смета от этого стала ТОЧНЕЕ, а не больше: стенд `minirun` гонял edit на побайтно ТОМ ЖЕ исходнике (sha256 `0b5f9b02…`) за $0.092086 июльскими, что и даёт ≈$0.38–0.40 сегодня. Число не пересматривалось. + +4. **Норма формы указателей заострена (уточняет D39.177 §2в): стабильна не ЗОНА, а ЧИСТЫЙ ФАЙЛ.** Номер строки ставится только туда, где `git status <цель>` показывает файл чистым; файл с незакоммиченной правкой — чей угодно, включая `docs/` — получает греп-указатель без номера. Ось «зона» была НЕВЕРНА и поймана опровергателем на моей же правке: `14-api-contract/openapi.yaml` лежит в зоне оркестратора и при этом двигается паком контракта 0.9.0. Носитель нормы — шапка `15-money-path.md`. + +5. **`arch-3` (major остатка аудита, строка 243) ЗАКРЫТ, и он был вдвое больше заявленного.** Аудит 30–31.08 называл «13 якорей, 9 не в тот код»; пере-проверка панелью нашла **51 указатель: 34 уехали, 1 мёртв, 16 верны**. Дороже адресов оказались КЛЕЙМЫ: дословная цитата контракта, которой в контракте нет; потерянный третий обязательный член `Usage`; живой К-13 при закрытом D39.132; метод `Settle`, которого у движкового `Store` нет; «смена потолка не двигает снапшот» без оговорки про `edit_ceiling_out`. **Корень механический:** 9 якорей из 12 РОДИЛИСЬ верными (`085dbb9`) и сгнили дрейфом, а гейт молчал по построению — по содержимому сверялись 89 якорей из ~818. Поэтому чинилась ФОРМА, не номера: сверяемых по содержимому стало **127**. diff --git a/docs/architecture/15-money-path.md b/docs/architecture/15-money-path.md index 47fc5e83..9125aa47 100644 --- a/docs/architecture/15-money-path.md +++ b/docs/architecture/15-money-path.md @@ -1,60 +1,67 @@ # Путь денег — от гранта до settle (топикальный вход, строка 167) -> **Статус: топикальный ВХОД, НЕ источник истины.** Док собирает денежную тему, размазанную по D-нотам, реестру дефектов платформы, глоссарию и бэклогу, в один маршрут чтения. При конфликте с чем угодно побеждает `docs/architecture/05-decisions-log.md` (правило чтения — диета D39.125: живой файл + слайсы). Каждое утверждение здесь снабжено указателем на первичный носитель; формулы — дословно оттуда. Словарь терминов (холд · прирост · аргумент потолка · baseline · settle · leftover-reserved · SpendBound) — `docs/glossary.md` §«Деньги платформы» (`glossary.md:50`). +> **Статус: топикальный ВХОД, НЕ источник истины.** Док собирает денежную тему, размазанную по D-нотам, реестру дефектов платформы, глоссарию и бэклогу, в один маршрут чтения. При конфликте с чем угодно побеждает `docs/architecture/05-decisions-log.md` (правило чтения — диета D39.125: живой файл + слайсы). Каждое утверждение здесь снабжено указателем на первичный носитель; формулы — дословно оттуда. Словарь терминов (холд · прирост · аргумент потолка · baseline · settle · leftover-reserved · SpendBound) — `docs/glossary.md` §«Деньги платформы» (`docs/glossary.md:55`=`**Деньги платформы:**`). +> +> ⚠ **Форма указателей — норма этого дока (31.08, пере-проверены ВСЕ указатели: 34 уехали, 1 мёртв, 16 верны).** +> **Стабильна не ЗОНА, а ЧИСТЫЙ ФАЙЛ.** Номер строки ставится только туда, где `git status <цель>` показывает файл чистым; файл с незакоммиченной правкой — чей угодно, включая `docs/` — получает греп-указатель без номера. Ось именно такая (D39.177 §2в), и зонная ось её НЕ заменяет: `docs/architecture/14-api-contract/openapi.yaml` лежит в «своей» зоне и при этом двигается прямо сейчас паком контракта 0.9.0. +> Токен-форма — `` `путь:N`=`ожидаемая подстрока` ``; её гейт `docs/scripts/counts.py --lint` сверяет ПО СОДЕРЖИМОМУ и краснеет, когда цель уезжает. +> ⚠ **Что гейт берёт с якоря БЕЗ токена:** существование файла и что номер не больше длины файла; у голого имени журнала решений — ещё и структуру (владелец строки обязан совпасть с названной рядом нотой). НЕ проверяется только СОДЕРЖИМОЕ — вот почему 34 якоря уехали молча. Голое имя без «/» не судится вовсе. +> ⚠ **Греп-форма не проверяется гейтом никак** (регексп якоря требует `:номер`) — осознанный размен: переживает чужой WIP, но молчит. +> ⚠ **Токен НЕ должен содержать бэктик:** регексп берёт токен как «всё до следующего бэктика» и обрезает хвост, а необрезанная часть не проверяется вовсе. ## 1. Канон одним абзацем -Деньги видимы с первого вызова: телеметрия, потолки, явное согласие на пере-оплату (CLAUDE.md, цель 5). Леджер без потолка запрещён — Р7: книга обязана нести хотя бы один из `book_usd`/`day_usd` (`backend/internal/config/book.go:250-251`, текст валидатора: «a ledger with no ceiling is forbidden, Р7»). Суммы на пользовательский экран НЕ выходят — запрет D39.84, подтверждён D39.100/К-8 («страница лимитов = СТАТУС, не суммы», D-лог `:196`) и держится в контракте (`14-api-contract/openapi.yaml:1152`: «No window, no `resets_at`, no sums»). Защита от перерасхода двойная и не зависит от событий: холд платформы делает кредит недоступным следующему прогону, а движок сам останавливается на потолке — «an overspend is impossible even while the platform is blind. The event stream is freshness only» (док-коммент `Hold`, `platform/internal/pgstore/credits.go:148-151`). +Деньги видимы с первого вызова: телеметрия, потолки, явное согласие на пере-оплату (CLAUDE.md, цель 5). Леджер без потолка запрещён — Р7: книга обязана нести хотя бы один из `book_usd`/`day_usd` (`backend/internal/config/book.go:321`=`a ledger with no ceiling is forbidden`, текст валидатора: «a ledger with no ceiling is forbidden, Р7»). Суммы на пользовательский экран НЕ выходят — запрет D39.84, подтверждён D39.100/К-8 (дословно: «страница лимитов/использования в настройках (СТАТУС, не суммы — запрет денег на экране D39.84 в силе и НЕ superseded)» — `docs/architecture/05-decisions-log.md:232`=`СТАТУС, не суммы — запрет денег на экране`) и держится в контракте (`docs/architecture/14-api-contract/openapi.yaml`, греп `State of the credit balance. No window` — ⚠ номер не ставим: файл двигает пак контракта 0.9.0). Защита от перерасхода двойная и не зависит от событий: холд платформы делает кредит недоступным следующему прогону, а движок сам останавливается на потолке — «an overspend is impossible even while the platform is blind. The event stream is freshness only» (док-коммент `Hold` — `platform/internal/pgstore/credits.go`, греп `an overspend is impossible even`). ## 2. Деньги ДВИЖКА (зона `backend/`) -- **Леджер SQLite.** Таблица `spend`, две фигуры: `committed_usd` (потрачено, с сырым ответом провайдера) и `reserved_usd` (зарезервировано под летящий вызов). `Reserve` перед каждым платным вызовом сверяет `SUM(committed_usd + reserved_usd)` книги и дня с потолками (`backend/internal/store/ledger.go:44,58,63`; Р7 — коммент `:12`); `Settle` конвертирует резервацию в committed и персистит ответ (`ledger.go:162,199-210`). Суммарно по книге — `SpentUSD` (`ledger.go:314-320`). +- **Леджер SQLite.** Таблица `spend`, две фигуры: `committed_usd` (потрачено, с сырым ответом провайдера) и `reserved_usd` (зарезервировано под летящий вызов). `Reserve` перед каждым платным вызовом сверяет `SUM(committed_usd + reserved_usd)` книги и дня с потолками (`backend/internal/store/ledger.go:44,58,63`=`func (s *Store) Reserve(`; Р7 — коммент `backend/internal/store/ledger.go:12`=`per-day (Р7); checked against`); `SettleWithCheckpoint` одной транзакцией конвертирует резервацию в committed и персистит ответ (`backend/internal/store/ledger.go:178`=`func (s *Store) SettleWithCheckpoint(`). ⚠ У ДВИЖКОВОГО `store.Store` метода `Settle` нет — он называется `SettleWithCheckpoint`; голое `Settle` пришло в прежнюю редакцию из словаря самого кода (`backend/internal/store/ledger.go:11`=`Reserve/Settle/ReleaseReservation semantics`). ⚠ Не спутать с ПЛАТФОРМЕННЫМ `credits.Settle` (§3 п.8) — это другой `Store`. ⚠ И прежний диапазон `199-210` — **ДРЕЙФ ЦЕЛИ, а не ошибка автора:** в `ledger.go` коммита `085dbb9`, где якорь ставился, строки 206-210 были ровно UPSERT настоящего settle (`committed_usd = committed_usd + excluded.committed_usd`), то есть якорь ПОДТВЕРЖДАЛ соседнее утверждение; сегодня тот же диапазон целиком лежит в ветке повторного settle («release the reservation, book no new spend»). Ровно этот дрейф и лечит токен-форма. Суммарно по книге — `SpentUSD` (`backend/internal/store/ledger.go:381`=`SpentUSD reports (committed, reserved)`). - **Гейт потолков — ПЕР-ВЫЗОВНЫЙ, и был им до эмиттер-пака** (`Reserve` на каждый свежий attempt; прежняя формулировка «на границе юнита» была неверна по отношению к потолкам — опровергнута приёмкой D39.131). На границе юнита сидел РЕПЭЙР-суб-бюджет — ужесточён до пер-вызовного с ценой вызова (строка 135 закрыта D39.131); эскалационный кап хоп НЕ прицениваает — перелёт ≤1 хопа, задокументирован и запинен (диспозиция D39.131 п.2д, реопен — живой инцидент). -- **leftover-reserved зануляется write-open.** `store.Open` (путь записи, каждый `translate`) выполняет `recoverReservations` (`backend/internal/store/store.go:88`, функция `:218`: `UPDATE spend SET reserved_usd = 0 WHERE reserved_usd > 0`) — файл владеется одним процессом, значит любой reserved на открытии принадлежит несеттлённому прогону. `OpenReadOnly` этого прохода намеренно НЕ делает (`store.go:95-104`) ⇒ **reserved из read-only `status` — всегда остаток мёртвого процесса** (несущий факт формулы PD-158, см. §3). -- **`--max-units` — ОБЪЁМНЫЙ потолок прогона, ортогональный денежному** (D39.165 §1б, принят D39.170). Ограничивает не деньги, а РАБОТУ: не больше N выходных ЮНИТОВ (гранулярность `units_total` манифеста — та же, в которой платформа продаёт главы) будет ОПЛАЧЕНО этим прогоном; юниты, отданные за $0 (резюм, ре-пин), ретраи и эскалации внутри юнита потолок не тратят. Принимает только `translate`. **Остановка по объёму — ЗАВЕРШЕНИЕ (exit 0), не пауза:** словарь кодов выхода не расширялся, различение живёт в отчёте прогона и в логе; отчёт разводит ДОСТАВКУ и ПЕРЕ-ДЕЛКУ. Носитель — `backend/internal/pipeline/volume.go`. ⚠ Проводка в платформу ГЕЙЧЕНА (`PD-422`): единственный писатель признака движения банка — дверь правок, рост АВТО-банка от майнинга флага не ставит. -- **`--ceiling-usd` — КНИЖНЫЙ потолок, не бюджет прогона.** Дословно из флага: «the book USD ceiling in force for THIS RUN ONLY — it OVERRIDES book.yaml `ceilings.book_usd` and is never written back. It caps the book's CUMULATIVE committed+reserved spend, not this run's increment» (`backend/cmd/tmctl/invocation.go:114`; ⚠-коммент `main.go:110`). Ратификация — D39.122 п.2(в): пересчёт «пользовательский прирост → абсолют» — обязанность ПЛАТФОРМЫ, вторая денежная ось не заводится. День-потолок флаг НЕ перекрывает (PD-157). Проводка внутри: `Ceilings.BookUSD` в `Reserve` и именование сработавшего потолка в ошибке — `backend/internal/pipeline/stagerun.go:479-480,505-511` (стоп = `errReserveCeiling`). -- **Потолки — wiring, не семантика:** `Ceilings` намеренно исключены из `BriefHash` («Wiring fields (paths, ceilings, db) deliberately excluded», `book.go:262-265`, канон `:267+`) ⇒ смена потолка не двигает ни снапшот, ни ре-билл (D39.110 п.2б). -- **`status --json` отдаёт фигуры платформе:** `committed_usd`, `reserved_usd`, `book_ceiling_usd`, `ceiling_pct` = 100·(committed+reserved)/book_ceiling (`backend/internal/pipeline/status.go:155-158`). Дневной фигуры в status нет (PD-157). -- **Леджер = НИЖНЯЯ граница** (строка 78 бэклога, `docs/PROGRESS.md:41`): «2xx body decode failed (call IS billed)» — провайдер списал, попытка в `request_log` не попадает; живой замер D39.86 — 3 вызова из 14, неизвестность $0.015111 при леджере $0.114378; движок сеттлит оценку и печатает `estimated-cost rows: 3`. Канал виден, фикс — money-паком (дизайн GENERALITY_PHASE2 §5.6). +- **leftover-reserved зануляется write-open.** `store.Open` (путь записи, каждый `translate`) выполняет `recoverReservations` (`backend/internal/store/store.go:110`=`s.recoverReservations(ctx)`; сама функция — `backend/internal/store/store.go:278-279`=`UPDATE spend SET reserved_usd = 0`) — файл владеется одним процессом, значит любой reserved на открытии принадлежит несеттлённому прогону. `OpenReadOnly` этого прохода намеренно НЕ делает (`backend/internal/store/store.go:124`=`does not run that pass`) ⇒ **reserved, увиденный read-only `status` В МОМЕНТ СПАВНА, — остаток мёртвого процесса** (несущий факт формулы PD-158, см. §3). ⚠ Прежняя редакция писала «ВСЕГДА остаток мёртвого процесса» — это ШИРЕ кода и снято 31.08: `OpenReadOnly` построен ровно затем, чтобы `status` работал ВО ВРЕМЯ живого прогона (`backend/internal/store/store.go:122`=`allows concurrent readers while a writer is live`), и конкурентный `status` покажет ЖИВУЮ резервацию между `Reserve` и settle. Узко формулирует и сам код: «after a run crashes, reserved_usd stays non-zero until the next WRITE command» (`backend/internal/store/store.go:130`=`after a run crashes, reserved_usd stays non-zero`), и платформа — «at spawn there is no other writer … so anything reserved is by construction a leftover, never a live promise» (`platform/internal/runs/spawn.go`, греп `never a live promise`). +- **`--max-units` — ОБЪЁМНЫЙ потолок прогона, ортогональный денежному** (D39.165 §1б, принят D39.170). Ограничивает не деньги, а РАБОТУ: не больше N выходных ЮНИТОВ (гранулярность `units_total` манифеста — та же, в которой платформа продаёт главы) будет ОПЛАЧЕНО этим прогоном; юниты, отданные за $0 (резюм, ре-пин), ретраи и эскалации внутри юнита потолок не тратят. Принимает только `translate`. **Остановка по объёму — ЗАВЕРШЕНИЕ (exit 0), не пауза:** словарь кодов выхода не расширялся, различение живёт в отчёте прогона и в логе; отчёт разводит ДОСТАВКУ и ПЕРЕ-ДЕЛКУ. Носитель — `backend/internal/pipeline/volume.go:12`=`the VOLUME ceiling — the run's second stop`; словарь флага дословно — `backend/cmd/tmctl/invocation.go:143`=`Stopping on it is a COMPLETION (exit 0), not a pause`. ⚠ Проводка в платформу ГЕЙЧЕНА (`PD-422`): единственный писатель признака движения банка — дверь правок, рост АВТО-банка от майнинга флага не ставит. +- **`--ceiling-usd` — КНИЖНЫЙ потолок, не бюджет прогона.** Дословно из флага: «the book USD ceiling in force for THIS RUN ONLY — it OVERRIDES book.yaml `ceilings.book_usd` and is never written back. It caps the book's CUMULATIVE committed+reserved spend, not this run's increment…» (`backend/cmd/tmctl/invocation.go:142`=`the book USD ceiling in force for THIS RUN ONLY`; ⚠-коммент `backend/cmd/tmctl/main.go:260`=`--ceiling-usd is NOT a per-run budget`; многоточие закрывает обрыв цитаты — во флаге дальше стоит «, and must be > 0»). Ратификация — D39.122 п.2(в): пересчёт «пользовательский прирост → абсолют» — обязанность ПЛАТФОРМЫ, вторая денежная ось не заводится. День-потолок флаг НЕ перекрывает (PD-157). Проводка внутри: `Ceilings.BookUSD` в `Reserve` и именование сработавшего потолка в ошибке — `backend/internal/pipeline/stagerun.go:492`=`BookUSD: r.bookCeilingUSD()` и `backend/internal/pipeline/stagerun.go:521-523`=`overrides the book's ceilings.book_usd` (стоп = `errReserveCeiling`, `backend/internal/pipeline/escalation.go:49`=`var errReserveCeiling`). +- **Потолки — wiring, не семантика:** `Ceilings` намеренно исключены из `BriefHash` («Wiring fields (paths, ceilings, db) deliberately excluded», `backend/internal/config/book.go:334`=`Wiring fields (paths, ceilings, db) deliberately excluded`; канон — `backend/internal/config/book.go:350`=`canon := struct {`) ⇒ смена ДЕНЕЖНОГО потолка не двигает ни снапшот, ни ре-билл (D39.110 п.2б). ⚠ Уточнено 31.08: это верно ровно про деньги. Потолок СЕГМЕНТАЦИИ `edit_ceiling_out` в снапшот ВХОДИТ и меняет границы чанков (`backend/internal/pipeline/snapshot.go:34`=`EditCeilingOut int`) — прежняя безоговорочная формулировка читалась как «любой потолок безопасен». +- **`status --json` отдаёт фигуры платформе:** `committed_usd`, `reserved_usd`, `book_ceiling_usd`, `ceiling_pct` = 100·(committed+reserved)/book_ceiling (`backend/internal/pipeline/status.go:285`=`book_ceiling_usd,omitempty`; арифметика `ceiling_pct` — `backend/internal/pipeline/status.go:749`=`100 * (committed + reserved)`). Дневной фигуры в status нет (PD-157 — `platform/docs/DEFECT_REGISTER.md`, греп `PD-157`). +- **Леджер = НИЖНЯЯ граница** (строка 78 бэклога, `docs/PROGRESS.md:84`=`Леджер денег = НИЖНЯЯ граница`): «2xx body decode failed (call IS billed)» — провайдер списал, попытка в `request_log` не попадает; живой замер D39.86 — 3 вызова из 14, неизвестность $0.015111 при леджере $0.114378; движок сеттлит оценку и печатает `estimated-cost rows: 3`. Канал виден, фикс — money-паком (дизайн GENERALITY_PHASE2 §5.6). ## 3. Деньги ПЛАТФОРМЫ по шагам (зона `platform/`) -1. **Грант.** `Grant` — строка леджера `kind='grant'`, идемпотентная по `(аккаунт, source, source_id)`: `on conflict (user_id, source, source_id) do nothing` (`platform/internal/pgstore/credits.go:38-49,357-362`). Фри-тир — тот же механизм в ТОЙ ЖЕ транзакции, что создание аккаунта (`pgstore/identity.go:86-88,153-155`; ⚠ **дефолт `SignupGrantMicroUSD` — НОЛЬ** (`platform/internal/config/config.go:262`), а не «5 × 1_000_000», как утверждала прежняя редакция: предложение «на бете дефолт в НОЛЬ» (D39.110 п.3) ИСПОЛНЕНО, `PD-104` в регистре стоит `fixed(P7)`. ⚠ Испр. аудитом доков 28.08 (D39.167) — ложная сумма стояла в ЕДИНСТВЕННОМ месте, куда идут за деньгами. -2. **Выбор потолка — шкала в ГЛАВАХ** (D39.110: от 1 до максимума, ноль не выбираем; потолок принадлежит ПРОГОНУ, в `book.yaml` не пишется). **Максимум = Balance КАК ЕСТЬ** (D39.115 п.2а; эррата в шапке D-лога `:4`): `Hold` — дебет в момент взятия, баланс уже не содержит открытых холдов, повторное вычитание вдвое укорачивает шкалу. Реализация: `pricing.Model.Scale` — `affordable = balance / PerChapter`, кламп по остатку глав, дефолт = верх шкалы (ратифицировано D39.123 п.2е; компромисс «весь баланс под одну книгу» принят осознанно) — `platform/internal/pricing/pricing.go:75-98`. -3. **Ставка $0.03/глава — конфиг-константа платформы:** `DefaultPerChapter = 30_000` micro-USD (`pricing.go:33`), переопределяется `PerChapterMicroUSD` (`config.go:90-93,194,229`). ⚠ **Провенанс ниже — ИСТОРИЯ, а не действующее обоснование (D39.165 §1, 28.08):** реальная цена главы замерена по движковым леджерам — средняя **$0.0115–$0.0190**, максимум одной главы $0.0375, то есть константа завышена в **1.6–2.6×**, и «купить 10 глав» отдаёт движку $0.30, покупающий ~16–26 глав («денежная ручка с подписью „главы"», `PD-410`). РЕШЕНО: цена — от ОБЪЁМА ИСХОДНИКА (носитель `chapters.units_total`, пишется интейком за $0), настоящий стоп — объёмный потолок В ДВИЖКЕ (см. §2). ⛔ Константу по сегодняшним числам НЕ калибровать (D39.165 ⛔г) — гейт строка 202. Исторический провенанс: exp08 v2 $0.0219/глава → ревизия D30.4 +15–25% → округление ВВЕРХ (низкая ставка опаснее высокой — прогон встанет на потолке посреди книги; `pricing.go:19-33`). Временная мера беты — честная оценка движковая (строка 166). -4. **Холд прироста ДО спавна.** `holdTx` пишет отрицательную строку `('hold', -amount)` и тем же знаком двигает кэш баланса (`credits.go:163-206,375-385`); нехватка — `ErrInsufficientCredit`. Холд + прогон + попытка + очередь — одна транзакция (`pgstore/runs.go:104` StartRun, `:338` рестарт; форма D39.123 п.2д). -5. **Аргумент движку: `bookCap = committed + прирост×ставка`, БЕЗ reserved — PD-158, дословно из кода: `return m.committed + increment`** (`platform/internal/runs/spawn.go:195-196`). Обе фигуры читаются ОДНИМ вызовом `status --json` перед стартом (`bookMeter`, `spawn.go:206-223`); отсутствие любой из них = отказ спавна («absent is not zero», PD-40). Почему без reserved: write-open движка занулит leftover до первой резервации, значит цифра из read-only status к моменту сравнения уже мертва; ратифицированная ранее формула с reserved переплачивала запасом ровно на leftover сверх холда — исполнено обеими формулами против гейта (D39.123 п.2б, поправка к D39.122(в)). Аргумент ≠ холд: он больше холда на committed (глоссарий `:50`). Фактически ушедшее значение хранится в `run_attempts.ceiling_arg_micro_usd` (PD-144, миграция 00011); потолок уходит argv-шаблоном `{{usd}}` (`config.go` `CeilingArg`, `spawn.go:132-150`); строка 145 закрыта D39.122. -6. **Baseline и списание.** `baseline = committed` книги на старте попытки (`platform/internal/runs/spawn.go:82`=`baseline, bookCap := m.committed`); списание попытки = разница счётчика с baseline; счётчик ниже baseline = подменённая БД, сеттлится в ноль с WARN — сами цифры в лог не идут (D39.84; `reconcile.go:308-316`). +1. **Грант.** `Grant` — строка леджера `kind='grant'`, идемпотентная по `(аккаунт, source, source_id)`: `on conflict (user_id, source, source_id) do nothing` (`platform/internal/pgstore/credits.go`, греп `func (s *Store) Grant(`; сама идемпотентность после пака sqlc уехала В ЗАПРОС — `platform/internal/pgstore/queries/credits.sql`, греп `on conflict (user_id, source, source_id) do nothing`). Фри-тир — тот же механизм в ТОЙ ЖЕ транзакции, что создание аккаунта (`platform/internal/pgstore/identity.go`, греп `appendLedger(ctx, tx, userID, "grant"`; ⚠ **дефолт `SignupGrantMicroUSD` — НОЛЬ** (`platform/internal/config/config.go`, греп `SignupGrantMicroUSD:`), а не «5 × 1_000_000», как утверждала прежняя редакция: предложение «на бете дефолт в НОЛЬ» (D39.110 п.3) ИСПОЛНЕНО, `PD-104` в регистре стоит `fixed(P7, дерево сессии)`). ⚠ Испр. аудитом доков 28.08 (D39.167) — ложная сумма стояла в ЕДИНСТВЕННОМ месте, куда идут за деньгами; якоря переведены на греп-форму и закрыта висевшая скобка — 31.08. +2. **Выбор потолка — шкала в ГЛАВАХ** (D39.110: от 1 до максимума, ноль не выбираем; потолок принадлежит ПРОГОНУ, в `book.yaml` не пишется). **Максимум = Balance КАК ЕСТЬ** (D39.115 п.2а; эррата в шапке D-лога — `docs/architecture/05-decisions-log.md:4`=`Balance КАК ЕСТЬ`): `Hold` — дебет в момент взятия, баланс уже не содержит открытых холдов, повторное вычитание вдвое укорачивает шкалу. Реализация: `pricing.Model.Scale` — `affordable = balance / PerChapter`, кламп по остатку глав, дефолт = верх шкалы (ратифицировано D39.123 п.2е; компромисс «весь баланс под одну книгу» принят осознанно) — `platform/internal/pricing/pricing.go`, греп `func (m Model) Scale(`. +3. **Ставка $0.03/глава — конфиг-константа платформы:** `DefaultPerChapter = 30_000` micro-USD (`platform/internal/pricing/pricing.go`, греп `DefaultPerChapter`), переопределяется `PerChapterMicroUSD` (`platform/internal/config/config.go`, греп `PerChapterMicroUSD`). ⚠ **Провенанс ниже — ИСТОРИЯ, а не действующее обоснование (D39.165 §1, 28.08):** реальная цена главы замерена по движковым леджерам — средняя **$0.0115–$0.0190**, максимум одной главы $0.0375, то есть константа завышена в **1.6–2.6×** — ⚠ но НЕ покрывает максимум: источник (D39.165 §1) добавляет «и отдельные главы её уже пробивают» ($0.0375 против $0.03 = ×1.25), и прежняя редакция это теряла, и «купить 10 глав» отдаёт движку $0.30, покупающий ~16–26 глав («денежная ручка с подписью „главы"», `PD-410`). РЕШЕНО: цена — от ОБЪЁМА ИСХОДНИКА (носитель `chapters.units_total`, пишется интейком за $0), настоящий стоп — объёмный потолок В ДВИЖКЕ (см. §2). ⛔ Константу по сегодняшним числам НЕ калибровать (D39.165 ⛔г) — гейт строка 202 бэклога (`docs/PROGRESS.md:182`=`Живой перевод книги НАСКВОЗЬ через API платформы`). Исторический провенанс: exp08 v2 $0.0219/глава → ревизия D30.4 +15–25% → округление ВВЕРХ (низкая ставка опаснее высокой — прогон встанет на потолке посреди книги; `platform/internal/pricing/pricing.go`, греп `Provenance, so the number is auditable`). Временная мера беты — честная оценка движковая (строка 166 бэклога, `docs/PROGRESS.md:154`=`Движковая поверхность оценки`). +4. **Холд прироста ДО спавна.** `holdTx` пишет отрицательную строку `('hold', -amount)` и тем же знаком двигает кэш баланса (`platform/internal/pgstore/credits.go`, греп `func holdTx(` — строку леджера пишет `appendLedger(… "hold", -amount …)`, кэш баланса двигает `MoveBalance` внутри неё); нехватка — `ErrInsufficientCredit`. Холд + прогон + попытка + очередь — одна транзакция (`platform/internal/pgstore/runs.go`, греп `func (s *Store) StartRun(`; рестарт — греп `func (s *Store) RestartRun(`; форма D39.123 п.2д). +5. **Аргумент движку: `bookCap = committed + прирост×ставка`, БЕЗ reserved — PD-158, дословно из кода: `return m.committed + increment`** (`platform/internal/runs/spawn.go`, греп `return m.committed + increment`). Обе фигуры читаются ОДНИМ вызовом `status --json` перед стартом (`bookMeter` — `platform/internal/runs/spawn.go`, греп `func (s *Service) bookMeter(`; отказы — греп `Absent is not zero`); отсутствие любой из них = отказ спавна («absent is not zero», PD-40). Почему без reserved: write-open движка занулит leftover до первой резервации, значит цифра из read-only status к моменту сравнения уже мертва; ратифицированная ранее формула с reserved переплачивала запасом ровно на leftover сверх холда — исполнено обеими формулами против гейта (D39.123 п.2б, поправка к D39.122(в)). Аргумент ≠ холд: он больше холда на committed (`docs/glossary.md:55`=`ЭТО НЕ ХОЛД`). Фактически ушедшее значение хранится в `run_attempts.ceiling_arg_micro_usd` (PD-144, миграция 00011); потолок уходит argv-шаблоном `{{usd}}` (шаблон — `platform/internal/config/config.go`, греп `CeilingArg`; подстановку делает `platform/internal/runner/engine.go`, греп `{{usd}}`; подстановку и рендер argv делает `platform/internal/runner/engine.go`, греп `func (t CeilingTemplate) Args(`; `platform/internal/runs/runs.go`, греп `func (s *Service) ceilingFor(` — однострочная делегация к нему — ⚠ прежняя редакция называла здесь `spawn.go`, который потолок только передаёт); строка 145 закрыта D39.122. +6. **Baseline и списание.** `baseline = committed` книги на старте попытки (`platform/internal/runs/spawn.go`, греп `baseline, bookCap := m.committed`); списание попытки = разница счётчика с baseline; счётчик ниже baseline = подменённая БД, сеттлится в ноль с WARN — сами цифры в лог не идут (D39.84; `platform/internal/runs/reconcile.go`, греп `the book's meter reads below`). 7. ⚠ **Терминальный вердикт ОПЕРАТОРА — путь холда мимо settle (добавлено 22.08, P8-FIX/D39.154).** Прогон, чей движок не ответит уже никогда, закрывает человек: `tmplatformctl run abandon --run --reason <текст> [--release-hold]`. Команда ОТКАЗЫВАЕТ, если попытка ещё называет юнит или несёт базовую линию траты — над живым движком прогон не закрывают. Холд возвращается ЦЕЛИКОМ в обоих случаях (к abandon допускается только попытка, не дошедшая до движка, значит она ничего не потратила), а флаг решает лишь КОГДА — сразу или на ближайшем свипе. До этого пака такого пути не было вовсе: холд чужого аккаунта висел, пока кто-нибудь не залезет в БД руками. -8. **Settle по фигуре движка.** `reconcile.settle` (`platform/internal/runs/reconcile.go`, греп `func (s *Service) settle(` — ⚠ номера строк в файлы живой зоны не ставим, пока её сессия работает) читает committed из status; `credits.Settle` одной транзакцией закрывает резервацию, возвращает холд и списывает spent; **spent > холда — капится холдом** с явной пометкой «capped at the hold; the engine reported …» (`credits.go:214-234`), перерасход движка счётом не абсорбируется. -9. **SpendBound (PD-159).** Отложенный settle читает пожизненный счётчик КНИГИ и без границы оплатил бы работу прогона-преемника (замер: $2.10 за прогон ценой $0.10, и преемник платил снова). Граница = наименьший baseline попыток той же книги, стартовавших ПОЗЖЕ: `committed = min(счётчик сейчас, SpendBound)` (`reconcile.go:293-307`; `pgstore/runs.go:268`). -10. **Открытое — эскроу/intent (строка 136, `docs/PROGRESS.md:85`):** write-ahead intent + состояние `uncertain`-эскроу + `closing`; холд НЕ освобождается по выходу юнита (сигнал обгоняет денежные события); spend не постится без свидетельства провайдера; расход сверх холда — полной суммой, превышение явной строкой убытка. Стройка по research/25, «скоро». До эмиттера (строка 103) платформа между спавном и settle слепа — защита держится холдом и потолком движка (§1). +8. **Settle по фигуре движка.** `reconcile.settle` (`platform/internal/runs/reconcile.go`, греп `func (s *Service) settle(` — ⚠ номера строк в файлы живой зоны не ставим, пока её сессия работает) читает committed из status; `credits.Settle` одной транзакцией закрывает резервацию, возвращает холд и списывает spent; **spent > холда — капится холдом** с явной пометкой «capped at the hold; the engine reported …» (`platform/internal/pgstore/credits.go`, греп `capped at the hold; the engine reported`), перерасход движка счётом не абсорбируется. +9. **SpendBound (PD-159).** Отложенный settle читает пожизненный счётчик КНИГИ и без границы оплатил бы работу прогона-преемника (замер: $2.10 за прогон ценой $0.10, и преемник платил снова). Граница = наименьший baseline попыток той же книги, стартовавших ПОЗЖЕ: `committed = min(счётчик сейчас, SpendBound)` (`platform/internal/runs/reconcile.go`, греп `bound != nil`; `platform/internal/pgstore/runs.go`, греп `func (s *Store) SpendBound(`). +10. **Открытое — эскроу/intent (строка 136 бэклога, `docs/PROGRESS.md:127`=`Write-ahead intent`):** write-ahead intent + состояние `uncertain`-эскроу + `closing`; холд НЕ освобождается по выходу юнита (сигнал обгоняет денежные события); spend не постится без свидетельства провайдера; расход сверх холда — полной суммой, превышение явной строкой убытка. Стройка по research/25, «скоро». ⚠ **«Строка 103» — ФАНТОМ, снят 31.08:** такой строки в бэклоге нет (закрыта D39.131), а эмиттер шва ПОСТРОЕН с обеих сторон — платформа тейлит `events.jsonl` и принимает кадр `spend` (`platform/internal/ingest/events.go`, греп `TypeSpend Type`). Денежной слепоты это НЕ снимает по построению: поток событий — только свежесть. Платформа между спавном и settle слепа — защита держится холдом и потолком движка (§1). ## 4. Фронт (зона `frontend/`) -Максимум шкалы — `Balance` КАК ЕСТЬ (D39.115 п.2а); `Reserved` — справочная величина, `ReadAccount` отдаёт обе раздельно (`pgstore/credits.go:99`). Шкала — в ГЛАВАХ: `CeilingBounds` — «in CHAPTERS. The chapters → money conversion lives on the platform and is not exposed here in any form»; клиенту запрещено клампить повторно; `max_chapters: 0` = состояние «исчерпано» вместо шкалы (`openapi.yaml:1116-1147`). Суммы на экран не идут: `Usage` = `state` (ok/low/exhausted) + `remaining_percent`, без окон и сумм (`openapi.yaml:1149-1165`); продуктовый носитель — ПТ-35 (`docs/product-requirements.md:62`): стоп по потолку = `paused` + оповещение «перевод остановлен: лимиты исчерпаны»; остаток — страница лимитов (S4+), слово владельца В-3, К-13 (`paused_reason` не различает «свой потолок» и «нет кредита»). +Максимум шкалы — `Balance` КАК ЕСТЬ (D39.115 п.2а); `Reserved` — справочная величина, `ReadAccount` отдаёт обе раздельно (`platform/internal/pgstore/credits.go`, греп `func (s *Store) ReadAccount(`). Шкала — в ГЛАВАХ: `CeilingBounds` — «in CHAPTERS. The conversion to money lives on the platform and is not exposed here in any form» (`docs/architecture/14-api-contract/openapi.yaml`, греп `The conversion to money lives on the platform`); клиенту запрещено клампить повторно; `max_chapters: 0` = состояние «исчерпано» вместо шкалы. ⚠ Прежняя цитата «The chapters → money conversion …» УСТАРЕЛА ВМЕСТЕ С КОНТРАКТОМ, а не была ошибкой автора: контракт пере-сверстал её в «The conversion to money …», а «run-ceiling scale» — в «run-limit scale». Сегодня `grep -c 'chapters → money'` = 0. Испр. 31.08 — и это цена дословной цитаты без токена, а не небрежность. Суммы на экран не идут: `Usage` несёт ТРИ обязательных члена — `state` (ok/low/exhausted), `remaining_percent` и `halt_reason` — и ни окон, ни сумм (`docs/architecture/14-api-contract/openapi.yaml`, греп `required: [state, remaining_percent, halt_reason]`; ⚠ третий член `halt_reason` добавлен контрактом ПОЗЖЕ этой строки дока — знак равенства устарел вместе с ним, испр. 31.08); продуктовый носитель — ПТ-35 (`docs/product-requirements.md:63`=`ПТ-35`): стоп по потолку = `paused` + оповещение «перевод остановлен: лимиты исчерпаны»; остаток — страница лимитов (S4+), слово владельца В-3. ⚠ **К-13 ЗАКРЫТ** (D39.132 п.2а: `null` на проводе; пере-суждён ревью 28 §4 №9 и НЕ опрокинут — с уходом дневной оси различие перестало быть продуктовым): прежняя редакция держала его живым различением «свой потолок / нет кредита», испр. 31.08 по ПТ-35. ## 5. Открытые дыры и их носители | Дыра | Носитель | |---|---| | ~~PD-113 (major)~~ **ЗАКРЫТ P6/D39.132** (испр. оркестратором 15.08): обе половины построены — событие `ceiling{scope}` + exit 4 (движок, D39.131) и потребитель (платформа, П-15): потолочный стоп = `paused` двумя независимыми каналами, причина пишется одним оператором со статусом и перечитывается после drain. ⚠ **Хвост «открытых major в регистре платформы 0» СНЯТ 22.08 — он протух:** ⚠ **прежняя редакция называла открытым мажором `PD-370` — он ЗАКРЫТ** (обе половины: зонная 22.08, контрактная минором 0.5.0 D39.161; регистр переведён 28.08 аудитом доков D39.167), и держать счёт чужого регистра здесь — лишний носитель, который протухает молча; счёт печатает `python3 docs/scripts/counts.py` | `platform/docs/DEFECT_REGISTER.md` (эра P6), D39.132 | -| ~~Строка 165~~ **ЗАКРЫТА D39.131** (испр. оркестратором 14.08): exit 4 = потолок · 5 = graceful stop · 10–19 полоса отказов; потребительская половина построена P6 (D39.132) — шов закрыт с обеих сторон; ⚠ exit 13 (`schema_mismatch`) едет паком migrate, ратификация направления — D39.132 | D39.131, D39.132 | -| **Строка 166** — движковая поверхность оценки $/глава; $0.03 — временная мера беты (П-10 зоны) | `docs/PROGRESS.md:114` | -| ~~**PD-104**~~ **ЗАКРЫТА** (`fixed(P7)`; дефолт гранта в коде — ноль, испр. 28.08 D39.167) — фри-тир: док↔код закрыто, продуктовая половина (ноль на бете · агрегатный потолок · счётчик) на владельце | `DEFECT_REGISTER.md:115`, D39.110 п.3 | -| **Строка 78** — леджер = нижняя граница (биллящийся decode-fail мимо request_log, ≤13% захода на ре-пробе) | `docs/PROGRESS.md:41` | -| **Строка 136** — эскроу/intent (см. §3 п.10) | `docs/PROGRESS.md:85` | -| *(добавка сборщика)* **PD-157 (minor)** — `day_usd` книги флагом не перекрывается, платформой не виден и не задаётся; в `status --json` дневной фигуры нет | `DEFECT_REGISTER.md:168` | +| ~~Строка 165~~ **ЗАКРЫТА D39.131** (испр. оркестратором 14.08): exit 4 = потолок · 5 = graceful stop · 10–19 полоса отказов; потребительская половина построена P6 (D39.132) — шов закрыт с обеих сторон; ⚠ exit 13 (`schema_mismatch`) ПОСТРОЕН паком migrate (D39.134; `backend/cmd/tmctl/main.go:72`=`exitSchemaMismatch = 13`), ратификация направления — D39.132 | D39.131, D39.132 | +| **Строка 166** — движковая поверхность оценки $/глава; $0.03 — временная мера беты (П-10 зоны) | `docs/PROGRESS.md:154`=`Движковая поверхность оценки` | +| ~~**PD-104**~~ **ЗАКРЫТА** (`fixed(P7, дерево сессии)`; дефолт гранта в коде — ноль, испр. 28.08 D39.167) — фри-тир: док↔код закрыто, продуктовая половина (ноль на бете · агрегатный потолок · счётчик) на владельце | `platform/docs/DEFECT_REGISTER.md`, греп `PD-104`, D39.110 п.3 | +| **Строка 78** — леджер = нижняя граница (биллящийся decode-fail мимо request_log, ≤13% захода на ре-пробе) | `docs/PROGRESS.md:84`=`Леджер денег = НИЖНЯЯ граница` | +| **Строка 136** — эскроу/intent (см. §3 п.10) | `docs/PROGRESS.md:127`=`Write-ahead intent` | +| *(добавка сборщика)* **PD-157 (minor)** — `day_usd` книги флагом не перекрывается, платформой не виден и не задаётся; в `status --json` дневной фигуры нет | `platform/docs/DEFECT_REGISTER.md`, греп `PD-157` | ## 6. Карта носителей темы | Вопрос | Где живёт | |---|---| -| Словарь терминов | `docs/glossary.md:50` §«Деньги платформы» | +| Словарь терминов | `docs/glossary.md:55`=`**Деньги платформы:**` | | Управляемый потолок: решение и форма | D39.110 (+поправка D39.112 п.3: п.1 — пересказ, не цитата) | -| Максимум шкалы = Balance как есть | D39.115 п.2а; эррата — шапка D-лога `:4` | -| Семантика `--ceiling-usd` | D39.122 п.2в; `tmctl/invocation.go:114`, `main.go:110` | +| Максимум шкалы = Balance как есть | D39.115 п.2а; эррата — шапка D-лога, `docs/architecture/05-decisions-log.md:4`=`Balance КАК ЕСТЬ` | +| Семантика `--ceiling-usd` | D39.122 п.2в; `backend/cmd/tmctl/invocation.go:142`=`the book USD ceiling in force for THIS RUN ONLY`, `backend/cmd/tmctl/main.go:260`=`--ceiling-usd is NOT a per-run budget` | | Формула аргумента потолка (без reserved) · ставка · settle-формы | D39.123 п.2б/г/д; PD-158/PD-159/PD-144 в реестре | | Код денег платформы | `platform/internal/pgstore/credits.go` · `pgstore/runs.go` · `runs/spawn.go` · `runs/reconcile.go` · `pricing/pricing.go` | | Код денег движка | `backend/internal/store/ledger.go` · `store/store.go` · `pipeline/stagerun.go` · `pipeline/status.go` · `config/book.go` |