# Журнал зоны «Платформа» > Весь прогресс платформы — ЗДЕСЬ (решение владельца 04.08): пинги, итоги сессий, открытые > вопросы, предложения на ратификацию. В `docs/PROGRESS.md` платформа не пишет; оркестратор > читает этот журнал при каждом лендинге зоны (свип «решений владельца» — норма D39.99 п.4). ## Текущее состояние - **P8-FIX ПРИНЯТ И ЗАЛЕНДЕН оркестратором №18 (22.08).** Приёмка велась слепой линзой ДО чтения отчёта: сперва пере-прогон, счёт тестов и собственные посадки, и только потом сверка с запиской зоны. **Что сверено ИСПОЛНЕНИЕМ, а не принято на слово:** - **Батарея на моём стенде:** 18 пакетов, exit 0, линтер «0 issues» — сходится. ⚠ **«Скипов 0» НЕ воспроизведён: у меня три.** Это не регрессия пака и не ошибка отчёта — `systemdOrSkip` гейтит три теста `internal/runner` достижимостью пользовательского менеджера systemd, а на моём хосте `/run/user/1000` не существует. Отчёт мерил на хосте с живым менеджером и там 0. Незакрытым остаётся то, что рецепт объявляет у батареи ДВА гейта, а их три — `PD-374`. - **Тесты 561 против 532 на HEAD** — сходится, сверено `git show HEAD:<файл>` по всем `_test.go` против дерева. Удалённых имён ровно **три**, все три описывали снятую пер-термную подпись. - **Отпечатки миграций:** пере-считаны все 41, расхождений нет; в диффе `migrations.sha256` ровно одна ДОБАВЛЕННАЯ строка — заявление «релизные миграции не тронуты» держится механически. - **Пол поверхности 15 → 14** равен реальной длине таблицы маршрутов (14), то есть запаса не оставлено и следующее сужение гейт поймает. Причина стоит в комментарии рядом. - **Гейты доков:** `--check` exit 0 · `--lint` exit 0. **Пинг №1 зоны закрыт:** якорь `05-decisions-log.md:542` → `perf_test.go:13` жив, токен «96% of the page» на месте. Правку ЧУЖОГО промта (пере-нацеливание двух моих якорей после переезда констант) **утверждаю и не откатываю**: якоря умерли по вине пака, пак их починил и раскрыл это сам — это ровно то поведение, которого норма и требует. - **Деньги — двумя путями из СЫРОГО леджера, своей живой пробой** (свой дев-демон на дереве пака, своя БД): до `run abandon --release-hold` кэш `account_balances` = 24910000 и `sum(credit_ledger)` = 24910000 при одном открытом холде 90000; после — 25000000 и 25000000, открытых холдов 0, строки `grant · hold · hold_release`. Холд вернулся целиком, книга разморозилась (следующий прогон 202). - **Вся цепь PD-169 воспроизведена мной живьём, а не по отчёту:** отсрочка считает неудачи между проходами (`FAILS 6`), переходит `StalledAfter` → гейдж `tm_platform_runs_stalled 1` на проводе → `tmplatformctl runs --stalled` называет прогон, счёт, что держит и НАСТОЯЩУЮ ошибку движка. Обе новые ручки печатаются на буте с источником (`SWEEP_BUDGET 2m0s default`, `RUN_BUDGET 1m0s default`). - **Одиннадцать собственных посадок мутаций вне списка автора.** Убиты восемь: разделение фазы · «бюджет выбран = отказ» · очистка счёта тишиной · бэкофф · пропуск фазы расчёта на исчерпанном проходе · версия контракта · пол идентичностей манифеста · «стоп перевешивает отсрочку». ⚠ Две первых редакции моих посадок дали ЛОЖНЫЕ «выжила» — я гонял не тот пакет (пин лежал в `internal/runs`, а мутация была в `internal/pgstore`); пере-прогнано, обе убиты. Говорю об этом, потому что необъявленная ошибка харнесса приёмки — это находка, которой нет. - **Выжили три, и они разные.** `StalledAfter` — законно: носитель один, пин ездит на константе. `maxAttempts` — **`PD-373`**: второй носитель есть в рантбуке прозой. `truncateReason` в `DeferRun` — **`PD-372`**: пин ездит на самой функции, ни один из четырёх вызовов не проверен. - **Находка вне карты пака — `PD-371`:** исключение «стоп перевешивает отсрочку» не имеет границы, и это видно живьём: `NEXT TRY` стоял на 30 минут вперёд, а счётчик рос каждые 15 секунд. **Регистр после приёмки: 374 строки, открытых 72 (1 major, 16 minor, 55 info)** — своих строк четыре (`PD-371`…`PD-374`); зонные числа сдачи (370/68) остаются верны на момент сдачи и не переписываются. **Открытым уезжает `PD-370`** (контрактная половина) — это НЕ работа зоны, и закрывать её здесь было бы подгонкой под критерий приёмки; согласен с диспозицией. ⚠ **Порядок паков вышел ОБРАТНЫМ моей очереди, и это меняет рамку следующего:** промт P8-FIX строкой 3 требует запуска «ПОСЛЕ пака P8-REVIEW», запущен он был раньше. Следствие в плюс: цена, которую я назвал вслух при перестановке — «блокер свипа живёт всё время читающего пака», — НЕ заплачена, блокер закрыт первым. Следствие в минус: §4.7 (аддендум релеем) в этом паке пустой и честно назван таковым, а читающий пак теперь пойдёт по коду, который этот пак только что переписал — его промт по оси 3 зона уже пере-нацелила на новые места, остальные три оси не затронуты. - **P8-FIX — ФИКС-ЛИСТ ПРИЁМКИ P7 ЗАКРЫТ. Дерево готово к приёмке и лендингу (22.08).** Батарея: 18 пакетов, exit 0, **скипов 0**, линтер 0 issues, скипов 0, тестов **561** (HEAD 532). Регистр — **370** строк, открытых **68** (14 minor, 53 info, **1 major**) — сверено командой на момент сдачи (`python3 docs/scripts/counts.py --check`, exit 0; `--lint` exit 0; ⚠ вес регистра этим скриптом НЕ проверяется — числа сняты его печатью, а не гейтом). **Открытый major — `PD-370`**, контрактная половина спила пер-термной подписи банка: она не работа зоны, закрывать её здесь было бы подгонкой под критерий приёмки. Миграция пака: **00025**. Дерево не закоммичено и не застейджено. **Удалённых имён тестов ТРИ, и все три — по решению владельца 22.08** (сверено исполнением, не памятью: имена из `git show HEAD:<файл>` по всем `_test.go` против дерева): `TestABadDecisionRefusesTheWholeBatchAndNamesTheItem` · `TestAnUnknownTermRefusesTheBatch` · `TestTheBankIsReplacedWhileTheDecisionsSurvive` — они описывали пер-термную подпись банка, которую `D39.144` отменил, а этот пак снял из кода (`PD-370`). Тест снятого кода — не потеря покрытия; третий переписан на СОХРАНИВШЕЕСЯ свойство (`TestTheBankIsReplacedFromTheEnginesReadOut`). Плюс один ПЕРЕИМЕНОВАН по факту изменения смысла (`…GivesTheHoldBackOnlyWhenAsked` → `…AlwaysGivesTheHoldBack`, `PD-361`) — имя заведено этим же паком. ⚠ **Пинг оркестратору №1 — ЕГО зона, сверить при лендинге:** `docs/architecture/05-decisions-log.md:542` держит якорь на `internal/pgstore/perf_test.go:13` с токеном «96%». Пак ЭТОТ комментарий правил (`PD-366`: он лгал про свой же бенчмарк), токен я сохранил намеренно и `--lint` зелёный — но док чужой, поэтому говорю, а не считаю вопрос закрытым. ⚠ **Пинг оркестратору №2 — попутная находка ВНЕ зоны пака:** `PD-369` / `П-21` — `ClaimIdempotency` под конкуренцией отвечает 500 (1–2 падения на ~80 прогонов собственного теста). Файл в диффе пака не участвует, не трогал. ⚠ **Пинг оркестратору №3 (`PD-370`, требует контрактной сессии):** канон 0.4.0 несёт пер-термный `BankDecision{action: approve|decline}`, а `D39.144` эту модель отменил 16.08 — подписывается весь банк одним ОК. Зона канон исполняет, снять в одностороннем порядке не может. Правка канона → правка зоны, в этом порядке. ⚠ **Сдача идёт ПОСЛЕ второй волны ревью**, которая нашла в самом паке блокер (`PD-353`) и два дефекта, делавших его собственные механизмы недостижимыми (`PD-354`, `PD-355`) — раздел «Волна 2». ### Записка-план по §4 промта: пункт → где исполнено / отказ с причиной | Пункт | Исход | Где | |---|---|---| | **Блокер свипа** (врезан первым) | **исполнен четырьмя механизмами** | ниже, отдельным разделом; `PD-169` пере-открыт и закрыт заново | | 1. `ContractVersion` 0.3.0 при формах 0.4.0 | исполнен | `internal/httpapi/capabilities.go` + гейт `internal/gates/contract_test.go` (`PD-328`) | | 2. Пины на два поля `EventBase` | исполнен, посадка сперва воспроизведена | `internal/pgstore/events_test.go` (`PD-329`) | | 3. Бюджет попыток материализации | исполнен | `internal/readmodel/readmodel.go`, миграция 00025 (`PD-330`) | | 4. sqlc + гейт разбора SQL | **гейт построен; sqlc — граница названа, решение владельца** | `internal/pgstore/sqlgate_test.go`; разбор — `PD-44`, строка бэклога **П-19** | | 5. `PD-297` (round-trip под блокировкой) | **отказ с причиной** | нечем и не на чем мерить — ниже | | 6. Труба доставки решений банка (строка 199а) | **не строил, сдаю вопросом** — как промт и требует | ниже, отдельным разделом | | 7. Мусор: задвоенная строка доккомментария | исполнен | `internal/books/books.go` `canTranslate` (`PD-335`) | | 8. Ревью-пак четырёх осей | **НЕ мой** — заказ пака P8-REVIEW | — | | 9. Один носитель на факт для замера 00016→00022 | исполнен, с названным расхождением с буквой пинга | `internal/pgstore/perf_test.go` (`PD-336`) | | 10. Пол на пустой манифест у материализатора | исполнен | `internal/ingest/manifest.go` `Whole()`, `internal/readmodel/readmodel.go` (`PD-332`) | | 11. `PD-327` стоит open при ратифицированном каноне | исполнен | регистр, `fixed(P8-FIX)` | | 12. Изоляция читающих чтений не запинена | исполнен, посадка сперва воспроизведена | `internal/pgstore/isolation_test.go` (`PD-333`) | | 13. `CreditHeldBy`: оговорка не запинена | исполнен, посадка сперва воспроизведена | `internal/pgstore/credits_test.go` (`PD-334`) | | `PD-255` (норма комментариев сменилась) | закрыт решением владельца 21.08 | регистр | ### Блокер свипа: почему лечение — не «поднять константу» Диагноз пинга подтверждён построчно и **воспроизведён исполнением**. Живое свойство: проход 2 мин против 60 с на прогон ⇒ двух медленных прогонов хватает, чтобы съесть проход целиком, после чего `ctx.Err()` обрывает цикл и `UnsettledRuns` — единственный ретрай отложенного расчёта — не вызывается ВООБЩЕ, для всей инсталляции, каждый проход. Построено: 1. **Проход разделён на фазы.** Реконсиляция получает половину остатка, расчёт — свой кусок (`runs.phaseBudget`). Выбранный бюджет фазы больше не ошибка свипа: очередь просто ждёт тика. 2. **Отсрочка с растущим бэкоффом на ПОПЫТКЕ** (`run_attempts.reconcile_after`, миграция 00025). Свип читает `RunsToReconcile`, а не `ListLiveRuns`; порядок «старейший первым» не тронут — он верен для всего, что просто медленно, — а из ГОЛОВЫ заклиненный прогон убирает именно отсрочка. Телеметрия (`Lag`) по-прежнему видит всё. 3. **Терминальное состояние после N неудач** (`runs.StalledAfter` = 5, ≈ четверть часа попыток): прогон считается ЗАСТРЯВШИМ, гейдж `tm_platform_runs_stalled`, одна ERROR-строка на пересечении порога — не на каждом тике. 4. **Ручка у ОПЕРАТОРА, которой не было вовсе:** `tmplatformctl runs [--stalled]` называет строку, число неудач, следующую попытку, сумму холда и его возраст, юнит и последнюю ошибку; `tmplatformctl run abandon --run --reason [--release-hold]` — терминальный вердикт. Плюс `books --abandoned` и `book refresh` для второй половины (материализация). **Границу «дальше не иду» называю явно:** автоматически решать про деньги прогона, о котором нельзя спросить, этот пак НЕ стал. Правило реконсилятора везде — «я не смог спросить» ≠ «прогона нет»; прогон, упавший N раз, это ровно прогон, о котором никто не смог спросить, и счётчик перед той же ошибкой её не чинит. Автоматический ответ на этот вопрос и есть эскроу (строка 136, П-18), который промт §4.6 из пака исключает. Что счётчик покупает — что человека ИЗВЕЩАЮТ; что покупает ручка — что извещение чего-то стоит. Хвост записан строкой **П-20**, а не подразумевается. Оба числа стали ручками: `TM_PLATFORM_SWEEP_BUDGET` и `TM_PLATFORM_RUN_BUDGET`. Второе — отдельная находка пака: поле `runs.Config.RunBudget` было объявлено, задокументировано и **никем не присваивалось** (`PD-331`), то есть ручка существовала в структуре и не делала ничего. ### Пункт 4: что построено и где проходит граница sqlc **Построен гейт, который доходит туда, куда sqlc не дойдёт:** `TestEverySQLStatementParsesAgainstTheMigratedSchema` сворачивает КАЖДЫЙ SQL пакета прямо из исходника (литералы, конкатенации, именованные константы, плюс оба внутренних помощника, принимающих SQL параметром) и планирует его живым Postgres'ом (`explain (generic_plan)`) против мигрированной схемы. **162 оператора, все планируются.** Несворачиваемый SQL — ОШИБКА гейта, а не пропуск: единственное исключение выписано таблицей в самом гейте (`store.go` `Ready`, где имя таблицы принадлежит goose). Тем же гейтом закрыт вопрос фикс-листа «есть ли в read-модели запрос, которого не касается ни один тест»: теперь его касаются все, на каждом прогоне батареи. **Граница sqlc — мой выбор, аргумент:** склеек в пакете 25 из 147, фрагментов-констант 15, у `lastRun` девять потребителей — read-модель для sqlc недостижима по построению, и это ровно та часть, где рантайм-ошибки и случались. Свободных от склейки файлов целиком пять: `credits.go`(15) · `identity.go`(13) · `idempotency.go`(7) · `sessions.go`(5) · `observe.go`(1) = **41 запрос**. Это единственный кусок, где инструмент силён; конверсия — отдельный пак (одиннадцать денежных запросов идут внутри чужой транзакции, генерённый код коммитится, нужен пин версии и гейт «сгенерённое актуально»). Взять его здесь на две-три ручки — ровно то, от чего предостерёг фикс-лист: купить инструмент туда, где не болит. Заведено строкой **П-19**. ⚠ **Вопрос закрыт владельцем 22.08: sqlc берётся ОТДЕЛЬНОЙ СЕССИЕЙ, не внутри пака** — изменений много и мешать их с содержательной работой нельзя. Граница остаётся названной: 41 запрос. ### Пункт 5, `PD-297`: отказ с причиной Мерить нечем и не на чем. Сама строка регистра объявляет замер на здешнем стенде НЕпредставительным (unix-сокет против управляемого PG по TCP — разница на два порядка), а корпусной книги нет: она появляется на холодном прогоне движка, которым гейчена строка 202 (решение владельца 20.08). Правка самого опасного на запись пути без замера — ровно то, что эта строка запрещает. Берётся вместе с холодным прогоном. ### Пункт 9: расхождение с буквой пинга, названное вслух Пинг просил свести условия В миграцию 00022. **Она РЕЛИЗНАЯ**, и гейт неизменности (`migrations.sha256` + `TestReleasedMigrationsAreUnchanged`) её правку запрещает; правка гейта ради этого была бы подгонкой под зелень, что промт и `CLAUDE.md` запрещают прямо. Поэтому 00022 остаётся нормативным носителем вакуумированных цифр как есть, а условия дописаны в носители, которые править законно. «96%» не удалено и получило своё условие: это ХОЛОДНЫЙ корпус акта 4 (636 мс против 24), тогда как 80–83% — вакуумированный. Носители мерили разное и все честны. ### Строка 199(а): развилку НЕ решал — сдаю вопросом, как требует §4.2 промта ⚠ **ПОПРАВКА ВЛАДЕЛЬЦА 22.08, и она сносит посылку, на которой я строил разбор.** Я описывал трубу как доставку ПЕР-ТЕРМНЫХ решений («approve/decline на термин»). **Такой модели нет и не было с 16.08:** D39.144 ратифицировал ОДИН ОК над ВСЕМ банком — «пер-термная подпись = сотни кликов — НЕ модель продукта», «в модели одного ОК сценария „какие 17 из 300 решены“ не существует». Пер-термно существует не подпись, а **правка**: поправить/добавить термин до подписи, и — после прочтения — поправить термин с пере-генерацией задетых глав (вторая половина гейчена строкой 192). Труба от этого не отменяется, но её ГРУЗ другой: не «решения», а **правки терминов** (`src` → новый `dst`, плюс добавленные) в `mined_delta`. Развилка ниже (кто объявляет ключи и кто создаёт файлы) от этого не меняется ни в одном пункте — она про владение `book.yaml`, а не про груз. ⚠ **Модель, которую D39.144 отменил, была у зоны ПОСТРОЕНА — 22.08 СНЯТА по слову владельца** (`PD-370`). Что ушло: маршрут `POST /books/{bookId}/bank/decisions`, хендлер и wire-типы, метод интерфейса `Library`, `SubmitBankDecisions`, типы `BankDecision`/`BankReceipt`, `UnknownTermError` и их пины. На месте write-пути — пометка для будущей сессии (`internal/httpapi/reading.go`, блок «THE WRITE HALF OF THE BANK IS GONE»): что отменено, что ратификация ОСТАВИЛА пер-термным (правку), и что под неё уже есть в схеме. ⚠ **Контрактная половина остаётся открытой** — канон 0.4.0 несёт путь, три схемы и глагол, и это зона контрактной сессии. Посылку промта пере-проверил САМ, чтением кода движка, а не по докам. Подтверждается вся: | Факт | Улика | |---|---| | движок читает `mined_delta`, только если путь объявлен | `backend/internal/pipeline/mining.go` `loadMinedDelta`: `if r.Book.MinedDelta == "" { return nil, nil }` — ни одного чтения | | то же для `mined_rejects` | там же, `loadMinedRejects` | | объявленный ключ с НЕсуществующим файлом валит загрузку конфига целиком | `backend/internal/config/book.go:199-207`: `bad("mined_delta %s is not readable: %v")` | | ключей нет в шаблоне оператора | `backend/example/book.yaml` — ноль вхождений `mined_delta`/`mined_rejects` | | платформа пишет `book.yaml` ОДИН раз и фиксированным набором из пяти ключей | `platform/internal/books/render.go` — `O_EXCL`, «writes the file once and never again»; ключи `book_id · title · source_lang · target_lang · source_file` | | **новое, чего в промте не было:** декодер движка СТРОГИЙ — незнакомый ключ это жёсткая ошибка | `backend/internal/config/book.go:142-144`, `dec.KnownFields(true)` — то есть платформа не может «на всякий случай» дописать ключ, которого сборка движка не знает | | относительные пути резолвятся от каталога КНИГИ | `backend/internal/config/book.go:148-162`, `resolve()` — значит один шаблон на все книги даёт пер-книжные пути без подстановки | | форматы файлов | `mined_delta` = `seed.File` (`terms:` со схемой `internal/seed/seed.go:63-80`), грузится `membank.LoadGlossarySeed` и пере-штампуется `Source:"mined"`; `mined_rejects` = `rejects: [{src, note}]` (`mining.go` `minedRejectFile`), это ФИЛЬТР ПРЕДЛОЖЕНИЙ, в банк не входит | | на стороне платформы решения write-only | `internal/pgstore/readmodel.go` `SubmitBankDecisions` пишет `action`/`dst`; ни один SELECT их не читает, воркер описан комментарием собственной миграции `00002_readmodel.sql:172-174` и не построен | **Варианты с ценой (решает владелец, не я):** - **A. Платформа объявляет два ключа при рождении книги и сразу создаёт два пустых файла.** Работает без единой правки движка и без действий оператора; пустой файл законен (`terms:` пуст ⇒ ноль записей, `os.Stat` проходит). **Цена:** платформа начинает знать два ключа СХЕМЫ движка, а форма Б ратифицирована ровно наоборот (D39.110 §2б: шаблон — деплой-артефакт оператора, платформа подставляет только личность, языки и имя файла). Плюс строгий декодер: сборка движка, которая этих ключей ещё/уже не знает, перестанет грузить КАЖДУЮ новую книгу. - **B. Ключи несёт шаблон оператора.** Платформа схемы не узнаёт, пути пер-книжные достаются даром. **Цена:** сам по себе вариант НЕ работает — движок отказывается грузить конфиг с объявленным, но отсутствующим файлом, а создавать файлы всё равно некому, кроме платформы. То есть B требует половины A или варианта D. - **C. Двигается ратифицированная граница владения:** платформе разрешено писать в каталог книги эти два файла (и только их), ключи несёт шаблон. **Цена:** правка ратификации D39.110; зато платформа по-прежнему не знает схемы — она пишет файлы по путям, которые прочитала из уже существующего `book.yaml`. - **D. Движок терпит объявленный, но отсутствующий `mined_delta`/`mined_rejects` как пустой.** Одна правка `backend/internal/config/book.go`, после чего работает чистый B: платформа пишет файлы только когда решения есть, и ничего не обязано существовать заранее. **Цена:** ослабляется намеренный гард — опечатка оператора в пути становится тихой. Смягчается тем, что послабление только для двух ключей и с громким WARN. Это строка ЕДИНОГО бэклога (движок), не моя. **Рекомендация зоны: C или D+B.** Обе оставляют платформу в неведении о схеме движка — а это и есть то свойство, ради которого форма Б ратифицировалась. A решает быстрее всех и покупает это тем, что платформа впервые начинает знать схему чужой стороны, причём под строгим декодером. ⚠ **Строить начну только после ратификации.** Границы, если разрешат, приняты как в §4.2: писать не «перед resume», а только на том резюме, которое СНИМАЕТ стоп подписи (`l.VerifyBank && !l.BankReleased`); `--verify-bank` не трогать; пост-ридинговый цикл (строка 192) не проектировать. `PD-277` в регистре относил эту работу к отложенной 192. ⚠ **22.08 он ПЕРЕ-ДИСПОЗИРОВАН** — не по развилке, а по спилу глагола: остаток «честность `decline`» умер вместе с ним. Ниже — прежняя формулировка обязательства, оставленная как след решения: правлю его только после ратификации, как промт и велит. ### Как это проверялось **Батарея:** `cd platform && make check` с ОБОИМИ гейтами (`TM_PLATFORM_TEST_DSN` на живом PostgreSQL 18.4, `TM_PLATFORM_TEST_ENGINE_BIN` + `_BOOK_TEMPLATE`) — 18 пакетов, EXIT=0, скипов 0, линтер «0 issues». Дифф имён тестов — исполнением: `grep -rho '^func Test[A-Za-z0-9_]*'` по дереву против `git show HEAD:<файл>` по всем `_test.go`. ⚠ **Замер пере-снят при сдаче** и его исход изменился: `comm -23` даёт ТРИ удалённых имени — все три описывали пер-термную подпись банка, снятую 22.08 по слову владельца (`PD-370`, разбор в шапке), — а `comm -13` даёт 29 новых. Прежняя формулировка «удалённых нет» была верна до спила и снята, а не оставлена рядом с новой. **Посадки мутаций волны 1 — 20, поймано 20** (перечень ниже; счёт пере-снят при сдаче — прежняя цифра «16» не сходилась с собственным списком в той же фразе). Каждая ставилась в код, пин обязан был упасть, код возвращался: снятие `payload["revision"]` ✅ · снятие `payload["structure_version"]` ✅ · `ContractVersion` назад на 0.3.0 ✅ · `inReadTx` без обеих опций ✅ · без изоляции ✅ · без read-only ✅ · `ListBooks` на `inTx` ✅ · снятие `and book_id <> $2` ✅ · снятие пола самоописания манифеста ✅ · снятие пола «ноль глав» ✅ · обе фазы свипа на ОДНОМ дедлайне ✅ · ранний возврат фазы из `Sweep` ✅ · свип читает `ListLiveRuns` вместо списка «пора» ✅ · отсрочка только по ошибке ✅ · `DeferReadModelDebt` в `now()` ✅ · долг никогда не списывается ✅ · новая граница наследует потраченный бюджет ✅ · колонка, которой нет в схеме, в СКЛЕЕННОМ фрагменте (гейт SQL) ✅ · то же в локальной `const q` ✅ · несворачиваемый SQL ✅. ⚠ **Три посадки из этого списка сперва ПЕРЕЖИЛИ** мои же пины и вскрыли настоящие дефекты пака — разбор в `PD-337`/`PD-338`/`PD-339`; пины переписаны, мутации пере-посажены. **Воспроизведение ПЕРЕД пином — там, где промт этого требовал (пп. 2, 12, 13):** три мутации посажены разом в HEAD-код и прогнана ПОЛНАЯ батарея — 18 пакетов, exit 0, скипов 0. То есть все три дыры подтверждены исполнением, а не прочитаны в пинге. **Живые пробы — настоящий `tmctl`, живой Postgres, дев-демон на 127.0.0.1:8098:** - `GET /v0/capabilities` → `"contract_version": "0.4.0"` (пункт 1 на проводе); - `tmplatformctl seed` — аккаунт, грант, книга через ЖИВОЙ `POST /v0/books`: интейк прошёл, движок нарезал 3 главы, `GET /chapters` отдаёт 3, долг материализации погашен, `read_model_attempts = 0`. **Это отрицательная проверка нового пола: законный манифест живого движка он НЕ отвергает;** - **PD-162 воспроизведён живьём** — каталог книги унесён, прогон принят (202), спавн падает. `tmplatformctl runs` печатает строку с числом неудач `1`, временем следующей попытки, суммой холда `0.090000`, его возрастом, `(none)` вместо юнита и текстом ошибки движка. **До этого пака оператор видел только растущий счётчик `sweep_unfinished_total` и не мог сделать ничего;** - `run abandon` без флага: прогон `failed`/`service_error`, деньги не тронуты, книга РАЗМОРОЖЕНА — следующий прогон принят (202). С флагом `--release-hold`: холд вернулся целиком; - **деньги пере-считаны из сырого леджера двумя путями и сошлись до цента:** кэш `account_balances.balance_micro_usd` = 5000000, `sum(amount_micro_usd)` по `credit_ledger` = 5000000, открытых холдов 0; строки леджера — `grant · hold · hold_release · hold · hold_release`; - метрики на проводе: `tm_platform_runs_stalled 0`, `tm_platform_reading_surfaces_abandoned 0`; - обе новые ручки конфигурации печатаются на буте с источником (`TM_PLATFORM_SWEEP_BUDGET` `2m0s` `default`, `TM_PLATFORM_RUN_BUDGET` `1m0s` `default`) — норма PD-114. **Гейты доков:** `python3 docs/scripts/counts.py --check` — exit 0, литералы сходятся; `--lint` — exit 0. ⚠ **Раскрываю правку ЧУЖОГО документа:** пак убил два якоря в `PLATFORM_P8_REVIEW_SESSION_PROMPT.md` (константы `sweepBudget`/`defaultRunBudget` переехали), и `--lint` был из-за этого КРАСНЫМ. Якоря пере-нацелены на новые места, абзац оставлен как описание НАЙДЕННОГО, а не текущего кода. Правил чужой промт сознательно и говорю об этом прямо: якоря умерли по моей вине, а красный гейт не обходят. Оркестратор вправе откатить эту правку — содержательного текста она не трогает. ⚠ Первая моя проверка этого гейта дала EXIT=0 и была ЛОЖНОЙ: я мерил в момент, когда файла с якорями в дереве не было, поэтому линт его не сканировал. Критик полноты был прав, я нет. ### Спил пер-термной подписи банка (слово владельца 22.08) и что осталось контракту **Заказ:** модель отменена `D39.144` ещё 16.08, реализации нет — спилить в зоне, оставить пометку будущим сессиям. Перед спилом — аудит воркфлоу: 5 линз (канон · код зоны · потребители · ратификация · будущая пометка) + адверсариальная проверка каждого рискованного вывода, **31 агент**. **Снято из зоны:** маршрут `POST /books/{bookId}/bank/decisions` · хендлер `bankDecisions` и wire-типы (`wireBankDecision`/`wireBankDecisions`/`wireBankReceipt`) · метод `SubmitBankDecisions` из интерфейса `Library` и из стора · типы `BankDecision`/`BankReceipt` · `ErrUnknownTerm` / `UnknownTermError` и их отображение в контрактный код · пины, которые их описывали. **НЕ тронуто, и это проверено:** чтение банка (`GET /bank`, `BankTerm`, оси статуса и происхождения) · вокабула ДВИЖКА `proposed|in_progress|approved` в сайдкар-инжесте — она чужая и не отменялась · сам стоп `awaiting_bank` и подпись как `resume` (D39.144 снимала ГЕЙТ, не СТОП) · таблица `bank_decisions` и её констрейнты: миграции релизные, а колонки `term_id`/`dst` — ровно то, что нужно будущей ПРАВКЕ термина. Данных под снос не было: на стенде ноль строк решений. **Пометка на месте write-пути** (`internal/httpapi/reading.go`, «THE WRITE HALF OF THE BANK IS GONE»): что именно отменено, что ратификация ОСТАВИЛА пер-термным (правку и добавление термина), чего нет ни на одной поверхности, и что под это уже есть в схеме и в движке (`mined_delta`). ⚠ **Гейт размера поверхности сработал** — `len(contractSurface) < 15` поймал снятие маршрута. Пол сдвинут 15 → 14 **с причиной в комментарии**: пол, опущенный под ратифицированное сокращение, — не подгонка; опущенный без неё — подгонка. Два теста, стоявшие на снятой ручке как на «любом POST», пере-нацелены на живой маршрут, а не удалены. #### Насколько страдает контракт (главный вопрос владельца) Канон **не лжёт про модель** — `D39.144` в него внесена и записана в четырёх местах верно (`openapi.yaml:443-448`, `:539-541`, `:707`, `:2498-2499`). Беда в другом: **рядом с новой моделью в полном рабочем обмундировании стоит старая** — путь, три схемы, четыре счётчика, — и обе зовутся словом «signing». Файл несёт ДВЕ модели подписи. - **Прямая ложь — шесть мест** (четыре в каноне, два в компаньоне). Худшие: `:1830-1832` («статус подтянется на следующей пересборке» — не подтянется, решения до движка не доезжают, что говорит сам же канон на `:1959-1963`) и компаньон `:672` («`POST /bank/decisions` НЕ ПОСТРОЕНО» — было построено в P7 и снято сегодня). - **Самопротиворечия — три.** Самое явное: `:443` «Signing the bank is ONE act over the whole of it» против `:477` «Signing is not a row edit» — заголовок описания ручки называет подписью САМУ РУЧКУ. Тридцать четыре строки между ними, и оба текста компилируются в сгенерированный клиент. Плюс `:1918-1922` («строка может быть решена и НЕ подписана, потому что отказ — тоже решение») — дословно отменённая модель — соседствует с `:1929-1932` («INFORMATIONAL, never a gate»). - **Дыра, которую спил вскрывает и которую надо назвать вслух:** `approve` + непустой `dst` был ЕДИНСТВЕННЫМ живым носителем того, что D39.144 оставила пер-термным — «поправить термин». А «добавить свой термин» модель называет, и в контракте его **нет вообще**: на 17 путей канона запись в банк была ровно одна. То есть после спила у собственной фразы канона `:539-541` («correct what they care to, then release it with ONE resume») **нет ручки**. - **Счётчики вырождаются.** `pending_decisions` считается как «строки `proposed`, на которые НЕТ записи в `bank_decisions`»; писателя больше нет ⇒ это «сколько строк предложено», а `complete` истинно ровно тогда, когда read-out движка не несёт ни одной `proposed` (не константа: сайдкар несёт и `in_progress`, и `approved`). Поля обязательные на проводе (`EventBank.required`), поэтому оставлены как есть: менять их смысл — правка канона, а не зоны. - **Объём и вес:** ~17 мест в `openapi.yaml` (~150 строк из 2592) + ~8 в компаньоне. Вес — **ломающий минор 0.4.0 → 0.5.0**; мажора нет ни по политике («0.x ломает миноры весь бета-период»), ни по факту. Цена фронту нулевая ДОПОЛНИТЕЛЬНО: его зеркало ратифицированно стоит на 0.2.3. **Пока канон не поправлен, деплой обслуживает на одну ручку меньше, чем объявляет.** Это записано строкой `PD-370` (зонная половина закрыта, контрактная открыта), и это осознанная цена спила, а не недосмотр. ### Волна 2: воркфлоу-ревью и что оно нашло в САМОМ паке Заказ владельца: после возврата ревьюеров волны 1 и починки их находок — воркфлоу пошире, 10–30 агентов, один Fable 5 на тяжелейший участок. Исполнено: **11 линз поиска + адверсариальный рефутер на КАЖДУЮ находку + синтез, 47 агентов, 0 ошибок**. Fable 5 — один, на свипе и деньгах. **36 находок, 21 пережила рефутера, 15 опровергнуто.** После склейки дублей (голодовку расчётной фазы назвали пять линз, срез по байту — две) это **17 строк регистра**: `PD-353…PD-366` закрыты, `PD-367`/`PD-368`/`PD-369` открыты — они не мои решения и переданы как вопросы. ⚠ **Итоговая строка самого воркфлоу («ни одна находка не пережила проверку») — БАГ МОЕГО скрипта синтеза, а не вердикт.** Она противоречила сама себе (пустыми были и «выжившие», и «убитые»), и таблица пере-собрана из `journal.jsonl` — по одной записи на агента. Говорю прямо: доверься я сводке, пак уехал бы на приёмку с блокером внутри. **Блокер, найденный ПЯТЬЮ независимыми линзами (включая Fable 5) и подтверждённый мной по коду до их вердикта — `PD-353`.** Пак разделил проход на фазы, чтобы одна не могла съесть другую, и оставил ВНУТРИ второй фазы то же самое голодание: `UnsettledRuns` упорядочен старейшим-первым, каждый элемент стоит целый бюджет, а `settle` при нечитаемой трате возвращает nil — то есть клин рапортует УСПЕХ, потратив проход. Одна недоступная книга замораживала холды ВСЕХ аккаунтов, каждый проход. Это ровно та формулировка, которую шапка `Sweep` объявляла побеждённой. **Ещё два, которые делали механизмы пака недостижимыми:** - `PD-354` — счётчик неудач обнулялся ТИШИНОЙ: дорогой вопрос движку задаётся раз в `ResyncEvery`, остальные проходы ничего не устанавливают, и `ClearRunDeferral` на них сбрасывал счёт. 1,0,1,0 — `StalledAfter` недостижим, гейдж пуст, оператору не говорят никогда. - `PD-355` — колонка SPENT в операторской таблице показывала пожизненную трату КНИГИ. Моя же починка G8 волны 1 взяла не то число, на единственной таблице, заведённой ради решения о деньгах. **Чему меня научили РЕФУТЕРЫ (15 опровержений, каждое куплено исполнением, а не рассуждением):** - «Поднять `RunBudget` = объявить здоровый прогон застрявшим» — **опровергнуто замером**: поднятие ручки не меняет вообще ничего, а строгая проверка `RunBudget < SweepBudget/2` отвергла бы сами шиппящиеся дефолты. Я уже держал эту «починку» в плане и не построил её. Остаток — `PD-368`. - «Интейк обязан применить `Whole()`» — **опровергнуто контрактом и батареей**: вся батарея интейка ездит на манифестах без списка глав, контракт интейка — только счётчики. Механика находки верна (`PD-367` открыт), лекарство — нет. - «Пропуск тела помощника в гейте SQL = дыра» — **опровергнуто исполнением**: мутация в теле `bump` краснит десятки поведенческих тестов; спрятать там непроверяемый оператор нельзя. - «Стойл-детектор слеп к форме PD-162» (находка Fable 5) — опровергнуто. **Две линзы разошлись во ВЕСЕ одной находки** (`PD-358`, срез по байту в операторской таблице): одна назвала микрополировкой вне рамки, вторая довела цепочку до многобайтового stderr движка. Починил как дешёвое и бесспорное, расхождение раскрыл в строке. ### Самопроверка (артефакт §5: имена агентов и по одной команде или цитате из каждого) Исходов три, «не проверено» ≠ «опровергнуто». Каждому агенту выдан СВОЙ каталог песочницы явным путём, мандат читающий, state-changing git запрещён перечислением (D39.113). В рабочее дерево не писал никто: оба ревьюера волны 1 ответили на прямой вопрос уликами. **Волна 1 — три изолированных ревьюера по финальному диффу (author≠reviewer):** | Агент | Ось | Цитата или команда ИЗ ЕГО отчёта | Находок | Исход | |---|---|---|---|---| | `agent-afec473415b228cdf` | контракт-конформность против канона 0.4.0 + свои посадки | «в песочнице создан симлинк `/docs -> …/docs` — новый гейт `internal/gates/contract_test.go` тянется за канон ЗА пределы модуля, и без него копия дерева его не находит» | 5 + 3 пережившие мутации | все 8 починены | | `agent-a394edded388676bc` | шов с движком и деньги, свой инвентарь каналов | «`repo*/platform/cmd/rev2probe\|rev2ctl\|rev2seam\|rev2ceiling\|rev2ids/main.go` и `repo*/platform/internal/runs/rev2_starve_test.go` — мои пробы (только в копиях)» | 8 + 3 пережившие мутации | все 11 починены; G1 — БЛОКЕР в моём коде | | `agent-a1e4407d89e52128d` | полнота сдачи: пункт заказа → носитель в дереве | «Snapshot taken at 2026-08-22 00:49:24. ⚠ The tree was moving while I read it: `platform/BACKLOG.md` gained rows П-19/П-20 at 00:48, mid-review» | 6 | 5 починены, 1 (К-4) вскрыла МОЮ ошибку измерения | Две разведки (read-only, не ревью): `agent-acf370d564663617e` — инвентарь SQL для границы sqlc; `agent-a1cb3d1a5c59f2f2e` — инвентарь каналов движка. **Волна 2 — воркфлоу `wf_f8d1f3e6-85a`, 11 линз (одна на Fable 5) + рефутер на каждую находку:** | Линза (агент) | Предмет | Цитата или команда ИЗ ЕГО отчёта | Находок | Пережило | |---|---|---|---|---| | `a2fdcd13144058b57` **Fable 5** | свип и деньги — тяжелейший участок | «Стойл-детектор слеп к клину „юнит исчез, workdir недоступен“ (форма PD-162): реконсиляция каждый проход рапортует успех» | 3 | 2 | | `ae8f856fe08ee9e61` | фазы и отсрочка свипа | «Счётчик отсрочек не может дорасти до StalledAfter для того самого клина, ради которого написан» | 4 | 3 | | `a3c4dc82a7d62f8a6` | архитектурный стиль, велосипеды | «Фаза расчёта денег осталась без отсрочки: head-of-line starvation, от которой пак и построен, на списке `UnsettledRuns` не починена» | 3 | 1 | | `a62786a00eb5bbae4` | каноны Go и каноны зоны | «RunBudget молча обрезан половиной SweepBudget» (опровергнуто) + голодовка расчётной фазы | 4 | 2 | | `af8620a80b7e12b06` | материализация и пол манифеста | «Провалившийся экспорт на смене кадра затирает весь оплаченный текст книги» | 3 | 3 | | `ada2420ac7c576856` | CLI, метрики, конфиг | «SPENT в операторской таблице — пожизненный счётчик КНИГИ, а не траты этой попытки» | 3 | 3 | | `ac92e0a8acba148ec` | сам гейт SQL: даёт ли ложную зелень | «`unresolvable` ключёвана функцией, а не сайтом: второй несворачиваемый запрос в той же функции молча проверяется текстом первого» | 2 | 1 | | `a21f43459f3f12f7e` | комментарии: вода против несущего | «perf_test.go копирует „нормативные“ цифры из миграции 00022 и в той же фразе утверждает, что не копирует» | 4 | 1 | | `a8faf63f1dde50903` | скоуп: сверка построенного против БУКВЫ промта | «Пункт 9: новое условие замера в perf_test.go ложно для бенчмарка этого же файла» | 4 | 2 | | `a4dad647c0eb8310f` | регрессии: что пак сломал в уже работавшем | «Отмена контекста сообщается как дедлайн: остановка демона посреди прохода поднимает счётчик голодания» | 3 | 2 | | `a178aca863af19915` | контракт-конформность построенного против канона 0.4.0 | «Пункт 1: подъём константы оставил в коде утверждение, что деплой объявляет 0.3.0» (опровергнуто) | 3 | 1 | Рефутеры (по одному на находку, 36 штук) считаются наравне с находками — три примера того, чем куплены их вердикты: - `ad12d9bcca05b883a`: «`WITH the index n_tup_upd=3942 n_tup_hot_upd=3909 (HOT 99.2%) idx size=8192` против `WITHOUT the index n_tup_upd=1994 n_tup_hot_upd=1984 (HOT 99.5%)`» — из этого замера выросла `PD-364` (индекс удалён); - `a337318688be20d30`: «`finished_at = now()` → `books.go:313: column "finished_at" of relation "books" does not exist (42703)` … на всех 5 сайтах»; - `a7fadd3c541e9a87d`: «пять проходов подряд дают „the sweep settled 2 of 3 finished runs: context deadline exceeded“, третий, чистый прогон не достигается никогда, reserved держится на $0.90» — это подтверждение блокера `PD-353` исполнением. **Посадки мутаций волны 2 — 6, поймано 6:** фаза расчёта не отсрочивает ✅ · `UnsettledRuns` игнорирует отсрочку ✅ · тишина снова считается здоровьем ✅ · `RunBudget` снова не присваивается ✅ · срез по байту в операторской таблице ✅ · управляющие символы не вычищаются ✅. ⚠ Две первые попытки посадить последние две дали НЕ падение теста, а ошибку сборки (осиротевший импорт) — это слабее, и они пере-посажены компилирующимися. ### Релей §4.7: не приходил Аддендум пака P8-REVIEW должен был доехать **только релеем оркестратора с эхо-подтверждением**. За сессию релея не поступало: ни находок по четырём осям, ни эхо. Отчёт читающего пака я не читал и не гадал по нему — промт это прямо запрещает, состав пака определяет оркестратор. Единственное касание того пака — техническое и раскрыто выше: два его якоря умерли от МОИХ правок кода, `--lint` покраснел, якоря пере-нацелены (`docs/PLATFORM_P8_REVIEW_SESSION_PROMPT.md:62`, плюс третье пере-нацеливание 22.08 после спила). Содержательного текста чужого промта не трогал. ### Obstacle: что не удалось и что не проверено - **Сквозного живого перевода через платформу не было** и в этом паке быть не могло: строка 202 гейчена холодным прогоном движка (решение владельца 20.08). Всё после «прогон стартовал и остановился» доказано батареей против живого Postgres и настоящего `tmctl`, а не переводом. - **Труба 199(а) не построена и форматы сверены ЧТЕНИЕМ**, а не записью файла: `mined_delta` и `mined_rejects` прочитаны в коде движка (`membank.LoadGlossarySeed`, `mining.go`), но ни одного файла платформа не писала — писать было бы строительством нератифицированной развилки. - **Замер `PD-297` не снят.** Отказ осознанный: путь самый опасный на запись, править его надо с замером на корпусной книге до и после, а корпусной книги в паке не было. - **Финальный дифф ПОСМОТРЕН** независимым ревьюером (author≠reviewer) уже после спила — он и закрыл эту строку obstacle. Три его major были настоящими дырами в МОЁМ коде, и все три — отсутствие пина, а не ошибка логики: пол `ErrTextUnknownForANewCut` (охраняет единственную запись, способную стереть оплаченный текст) · «стоп перевешивает отсрочку» в `RunsToReconcile` · «пересборка банка не трогает `bank_decisions`» (свойство, на которое опирается пометка, ради которой спил и делался). Каждая закрыта пином, каждый пин проверен посадкой мутации. Плюс семь минорных: комментарии `SaveBank` и `bankCountsTx` описывали отменённую модель как живую, пометка врала про `action NOT NULL`, снятие NUL не было запинено. Не найдено дефектов там, где он искал отдельно: пол поверхности 15→14 равен реальному размеру таблицы, пере-нацеленные тесты проверяют то же свойство, лишнего не срезано. - **Число скипов батареи** снято рецептом `make check`, который пишет `.check.log` в дерево; при параллельных прогонах агентов лог обрезался. Отдельная сверка двух пакетов с условными скипами (`internal/runner`, `internal/gates`) дала `--- SKIP` = 0. - **Контрактная половина `PD-370` открыта**: пока канон не поправлен, деплой обслуживает на одну ручку меньше, чем объявляет. Это цена спила, названная заранее, а не обнаруженная при приёмке. ### Инвентарь каналов движка (артефакт §4.4 — собран ЧТЕНИЕМ кода движка, не по нашим докам) Пак коснулся канала движка (пункт 10: `ingest.Manifest.Whole()` построен как зеркало движкового правила), поэтому инвентарь сдаётся таблицей, а не словами «я прочитал код движка». | Канал | Писатель в движке | Атомарность | Читатель/писатель платформы | Согласовано? | |---|---|---|---|---| | `.manifest.json` | `pipeline/manifest.go:379` `writeFileAtomic` | **атомарно** (temp+`Sync`+rename, `pipeline/artifact.go:24-59`) | `internal/runner/engine.go` (`tmctl manifest`) → `ingest.DecodeManifest` → `internal/books/parse.go`, `internal/readmodel` | ✅ **и стало строже**: `Manifest.Whole()` теперь зеркалит `BookManifest.selfConsistent` (`pipeline/manifest.go:460-483`) поле в поле по тем правилам, которые платформа вообще читает (главы, юниты, нумерация). Верхняя граница чанков движка у нас counterpart'а не имеет намеренно — платформа счётчиков чанков не берёт | | `.bank.json` | `pipeline/bankexport.go:154` `writeFileAtomic` | **атомарно** | `internal/runner/artifacts.go:37` → `ingest.DecodeBank` → `pgstore.SaveBank` | ✅ | | `.bank-stop.json` | `pipeline/mining.go:393` `writeFileAtomic` | **атомарно** | **читателя НЕТ** (грепом по зоне — ноль вхождений) | ⚠ канал существует и не потребляется; полная таблица подписи платформе сегодня не нужна, она проецирует банк | | `.bank-stop.txt` | `pipeline/mining.go:195` `os.WriteFile` | **НЕ атомарно** (усечение первым делом) | читателя нет | ✅ и это важно: брать его на живом прогоне нельзя — исправление пинга №15 оркестратором подтверждается кодом | | `.mined-signature.yaml` | `pipeline/mining.go:188` `os.WriteFile` | **НЕ атомарно** | читателя нет | ✅ то же | | `.auto-bank.yaml` | `pipeline/mining.go:741` `os.WriteFile` | **НЕ атомарно** | читателя нет | ✅ то же | | `mined_delta` (путь из `book.yaml`) | **писателя в движке НЕТ** — только читатель `pipeline/mining.go` `loadMinedDelta`; формат `seed.File` (`internal/seed/seed.go:63-80`), грузится `membank.LoadGlossarySeed`, `Source` пере-штампуется на `"mined"` | — | **писателя НЕТ и у платформы** | ❌ **разрыв — это и есть строка 199(а)** | | `mined_rejects` | читатель `pipeline/mining.go` `loadMinedRejects`; формат `rejects: [{src, note}]`; ФИЛЬТР ПРЕДЛОЖЕНИЙ, в банк не входит | — | писателя нет | ❌ тот же разрыв | | `events.jsonl` (NDJSON эмиттера) | движок, StreamVersion 1.1 | append-only | `internal/ingest/tail.go` + `pgstore.RunSink` | ✅ пак не трогал | | exit-коды `tmctl` | контракт движка | — | `ingest.OutcomeOf`, `internal/runs/reconcile.go` `outcome` | ✅ пак не трогал | | `tmctl status --json` | движок | — | `internal/runner/engine.go` → `ingest.StatusReport`; расчёт денег | ✅ пак не менял форму; изменил лишь то, СКОЛЬКО раз его зовут при неудаче (бэкофф) | | `book.yaml` | оператор (шаблон) + **платформа один раз** (`internal/books/render.go`, `O_EXCL`) | — | платформа ПИШЕТ пять фиксированных ключей | ⚠ **декодер движка СТРОГИЙ** (`config/book.go:142-144`, `KnownFields(true)`): незнакомый ключ = жёсткая ошибка. Значит любое расширение набора ключей платформой — ратификация, а не правка. Прямо относится к развилке 199(а) выше | - **P7, акт 5 — ЗАКРЫТ. Дерево готово к приёмке и лендингу.** Батарея: 18 пакетов, exit 0, скипов 0, линтер 0 issues, тестовых функций **532** (⚠ было «533» — испр. оркестратором №18 20.08: канонической командой D39.121 `grep -rh '^func Test' platform --include='*_test.go' | wc -l` выходит 532; расхождение на единицу, ни на что не влияет, но число было заявлено без команды). Регистр — 327 строк, открытых 65 (13 minor, 52 info), **major и BLOCKER нет**. Дерево не закоммичено и не застейджено. Миграции пака: **00019–00024**. ### Что построено и зачем 1. **Долг на материализацию — колонка с арендой** (`books.read_model_owed_at`, 00019). Ставится в ТОЙ ЖЕ транзакции, что закрывает границу работы (конец разбора и все три конца прогона), гасится сверкой метки, берётся в аренду тем, кто платит. **Зачем:** очередь жила в памяти процесса — упавший `Refresh` её терял, рестарт демона терял её всю, и оплаченный текст не доезжал до читателя НИКОГДА. Плюс «в покое» для потока теперь значит «и материализация не должна» — это Ф-56: книга, только что нарезанная, получала `end` и 204 «не переподключайся» ровно в те секунды, пока дерево ещё не легло. **Почему отошёл от прежней реализации:** удалены очередь в памяти (`runs.deferRefresh`/ `DrainRefresh`/`Reader`) и бэкстоп «дерева нет вовсе» (`books.materializeMissingTrees`, `pgstore.BooksWithNoTree`) — оба покрывались одним механизмом, а бэкстоп к тому же не видел книгу, у которой дерево есть, но на границу старше. Дрейн переехал в `readmodel.Drain` — в пакет, чья это работа. 2. **«Глава сделана» = последний проход, который книга на ЭТОМ деплое реально получает** (`books.edit_wave`, 00024). **Зачем:** счёт шёл по волне `edit`, и на деплое без редактора ни одна глава не была сделана никогда — шкала покупки не подрезалась, сервис бесконечно предлагал купить уже переведённое. Форму объявляет сам движок (пайплайн без редактора даёт волне `edit` знаменатель ноль). **Почему на КНИГЕ, а не на прогоне:** прочитанная с последнего прогона, она роняла `chapters_done` в ноль в момент допуска нового прогона — счётчик ходил назад, что канон запрещает. Флаг монотонный: книга, прошедшая редактирующий пайплайн, остаётся такой. 3. **У клейма идемпотентности появился владелец** (`claim_token`, 00020) и **тождество интейка решается дайджестом файла** (`content_sha256`, 00023). **Зачем:** строка ключа не несла, какая попытка её держит — застрявшая попытка удаляла строку преемника (пользователь получал вторую книгу) или писала свою квитанцию поверх чужой (клиент реплеил `Location` несуществующей книги); а тождество по `Content-Length` не различало две РАЗНЫЕ книги с одинаковым объявлением. 4. **Остановка АККАУНТА читается с аккаунта**, а не с потолка одного прогона; `resume` различает «прогон закончен, нужен новый» (`ceiling_reached`) и «пополните счёт» (`credit_unavailable`); `blocked` называет холд, который реально освободит шкалу. **Зачем:** всё это — «у вас нет денег» пользователю, у которого деньги есть, и отправка его делать не то. 5. **Счётчик замечаний вернулся на главу** (`chapters.note_count`, 00022), `chapters.units_done` снесён (00021). **Почему это разворот решения 00016 того же пака:** 00016 снёс колонку с доводом «её никто не пишет» — здесь она возвращается ВМЕСТЕ с писателем, которого ей не хватало. Основание замерено: джойн, которым её заменяли, стоил 83% времени страницы библиотеки (16.8 мс против 3.4 мс на корпусе 40 книг × 500 глав, ВАКУУМИРОВАННОМ); со счётчиком — 6.6 мс. ⚠ Условие замера названо здесь и при каждом другом числе про него (P8-FIX, PD-336): на ХОЛОДНОМ корпусе тот же джойн даёт 96%, и это не противоречие, а другой замер. Нормативный носитель вакуумированных цифр — миграция 00022, остальные на неё указывают. Бенчмарк оставлен в дереве: `go test ./internal/pgstore/ -bench LibraryPage -run xxx`. ### Что форсировано и почему - **Работа по канону 0.4.0 лежит в дереве до его ратификации.** Основание: ответ контрактной сессии на записку зоны плюс слово владельца 20.08 про «кончились деньги → сообщить и дать продолжить». Ломающая правка ровно одна — `Run.stop_requested` (0.3.0 такого поля не объявляет); `cause.code: credit_unavailable` легален и по 0.3.0, второй уровень там открыт. Зависимость записана строкой **PD-327** и закрывается лендингом канона, а не работой зоны. Если форма сдвинется словом владельца — правки отделимы, карта в `archive/P7_ACT5_FIX_PLAN_2026-08-20.md` §5б. - **Тесты на АТОМАРНОСТЬ сверяют `xmin`** (транзакцию, последней писавшей строку). Это не экзотика ради экзотики: два пина акта проходили под мутацией «вынести метку из транзакции во второй оператор» — конечное состояние она не меняет, меняет окно, и утверждать про окно иначе тест не может. ### Что осталось, названо и не спрятано - **PD-327** — ждёт лендинга канона 0.4.0 (не работа зоны). - **PD-297** — round-trip на строку в `SaveStructure` под эксклюзивной блокировкой книги. Не взят осознанно: путь самый опасный на запись, инструмент штатный (`tx.SendBatch`), но правку надо мерить на корпусной книге до и после. - **Четыре оси не смотрел никто и никогда:** деньги и леджер целиком · вход/сессии/CSRF · очередь и джобы · метрики. Пак их не менял, а все приёмки смотрели дифф. Это отдельный пак. - **Живого перевода на стенде не было ни разу** — движку нужны ключи провайдера и деньги. Всё после «прогон стартовал и остановился» (подпись банка, halt на потолке, резюм после пополнения) покрыто батареей против живого Postgres, а не живым движком. ### Как это проверялось `make check` с гейтами (скипов 0) · каждая правка проверена ПОСАДКОЙ МУТАЦИИ: код испорчен именно так, как называет комментарий пина, пин обязан упасть, код возвращён (40 проб, все поймались) · живые пробы по ручкам на стенде: интейк и реплей, `409 key_reused` на другой файл под тем же ключом, часть после файла на реплее, `/chapters` `/units` `/notes` `/bank`, ETag→304 и разные теги у страниц, 410 на чужой id главы, `after_version` из прошлой жизни, `unsupported_pair`, `/usage` на пустом счету и после `adjust`, старт и стоп прогона со `stop_requested` на проводе, SSE в покое и SSE с непогашенным долгом (Ф-56 на проводе), 400 вместо 413 на длинном теле. ### Самопроверка Акт закрыт адверсариальным ревью СОБСТВЕННОГО диффа (9 линз × 2 рефутера + критик полноты, 84 агента, 0 ошибок): **37 находок, 18 пережили рефутеров**, и почти все — дефекты, внесённые правками самого акта. Строки **PD-315…PD-327**, каждая с механизмом и пином. Два из них — собственные пины акта, проходившие под мутацией, которую сами называют (PD-318): это третье появление класса в паке, и он назван в плане §3 п.2 ещё до того, как повторился. - **P7, акт 5 (начало) — исполнение фиксов по ревью акта 4 + ответ контрактной сессии.** План и рабочий список — **`platform/docs/archive/P7_ACT5_FIX_PLAN_2026-08-20.md`** (читать первым; §5а — что пришло из контракта). ⚠ **Контрактная сессия ответила 20.08 на записку зоны `archive/CONTRACT_SYNC_FROM_PLATFORM_2026-08-20.md`: канон стал 0.4.0** (лежит в дереве контрактной зоны, НЕ ратифицирован — до лендинга в силе 0.3.0). Все три вопроса, висевшие на владельце, закрыты. Зоне пришла работа: `resume` исчерпанного прогона — 409 с двумя РАЗНЫМИ причинами (`ceiling_reached` против нового `credit_unavailable`); «глава сделана» = последний реально пройденный проход, и это ДЕНЬГИ — `ChaptersLeft` на деплое без волны редактора не подрезается вовсе, сервис бесконечно предлагает купить уже переведённое; отпечаток интейка — по содержимому, `Content-Length` из нормы вышел, а невозможность установить тождество обязана давать 409, а не реплей; `Run.stop_requested` обязательным булевым (единственная ломающая правка). Отклонены: слово под суточный потолок движка (решение владельца 15.08) и предложенный зоной признак «прогон продолжаем» (три лишних чтения на карточке, которую перечитывают на каждый кадр). Акт 4 закрылся адверсариальным ревью СОБСТВЕННОГО диффа (9 линз × 2 рефутера, 75 агентов): **33 находки, 27 пережили рефутеров**, и часть из них внесли сами правки акта 4. Тяжелейшая — четыре независимые линзы сошлись на том, что **реконсилятор снимал стоп подписи банка, которого никто не подписывал**, и доспавнивал движок без `--verify-bank`: складывая снятие стопа внутрь общего `reopen`, акт 4 не проверил, что `reopen` зовёт не только `resume`, но и свип. Нарушение согласия пользователя; починено ключом на вызывающего, пин проверен посадкой мутации. ⚠ **Первый написанный на это пин ПРОШЁЛ под мутацией** — фикстура не доходила до кода, потому что часы сервиса захвачены при конструировании и `f.now` их не двигает. Урок в плане, §3. Закрыто также: загрузочная проверка пар — на ДОСТУПНОЙ половине (деплой с одними `:unavailable` больше не стартует), интейк не трактует пустой список как «пускать всё», теги сравниваются регистронезависимо. Батарея: 18 пакетов, exit 0, скипов 0, тестов **503**. - **P7, акт 4 — доработка 20.08: сверка ВСЕХ находок против дерева + триаж §8.2.** (Пакет релеев `archive/P7_ACCEPTANCE_HANDOFF_2026-08-17.md` §7; три вопроса владельцу §8.1а; вопросы контракта вынесены в `platform/docs/archive/CONTRACT_SYNC_FROM_PLATFORM_2026-08-20.md` для отдельной контрактной сессии.) Все 74 позиции обеих приёмок сверены поштучно с ТЕКУЩИМ деревом — каждая с цитатой живого кода, исход «не проверено» отдельно от «опровергнуто». ⚠ **Три места числились закрытыми и закрыты не были — правка не закрывала собственный сценарий:** (1) подрезка буфера потока сверялась ПОСЛЕ того, как водяной знак перепрыгнул дыру, поэтому условие не срабатывало никогда и кадры `note` терялись молча (PD-283); (2) `CompleteIdempotency` писала квитанцию в строку, которая попытке уже не принадлежит — воспроизведено живым PG, клиент получал квитанцию ЧУЖОГО запроса (PD-284); (3) пин на снятие `--verify-bank` был ПУСТЫМ: фикстура стартовала прогон без `stop_for_signing` (PD-285). Все три «подтверждались» зелёной батареей. **Ещё закрыто:** HEAD на потоке больше не держит горутину и ошибка `Flush` не выдаётся за отправленный кадр (PD-286) · `blocked` называет чужую книгу только когда её холд укорачивает шкалу (PD-287, пина не было вовсе) · `note_count` и `GET /notes` описывают одно множество (PD-288) · устаревшая доставка не рождает кадр (PD-289) · чтения на одном снапшоте (PD-290) · флаг «текст прочитан» переехал на ПАРУ (PD-291) · снятие стопа банка внутри транзакции переоткрытия, отдельного писателя больше нет (PD-292) · проход материализации подрезается своим бюджетом (PD-293) · блокировка книги в решениях банка (PD-294) · дубликат индекса снят (PD-295) · маршруты и коды ошибок — по ОДНОЙ таблице, тесты ходят по ней (PD-296) · бэкстоп-свип для книги без дерева (PD-276 дозакрыт) · интейк не платит за третью ре-нарезку (PD-248 актуализирован). **Триаж §8.2:** 17 сделано, 5 отклонено с причиной, 1 релей. Батарея: 18 пакетов, exit 0, скипов 0, линтер 0 issues, тестов **458 → 501**; каждая правка проверена посадкой мутации, не рассуждением. ⚠ **Осознанно НЕ сделано, с причиной:** PD-297 (round-trip на строку под блокировкой книги — мерить прежде правки) · PD-202 (draft-only, вопрос владельцу) · PD-282 (канон §resumeRun не покрывает «денег не осталось») · PD-298 (сперва сверка у движка) · PD-299. **Осей, которых не смотрел никто:** деньги и леджер · вход/сессии/CSRF · очередь и джобы · метрики. - **P7: приёмка → правки → приёмка правок (17.08).** ⚠ Полный хендофф под сессию без контекста — **`platform/docs/archive/P7_ACCEPTANCE_HANDOFF_2026-08-17.md`** (читать §0–§3, дальше по нужде). Там же: принципы приёмки, сверка с исходным промтом, техдолг, план следующей сессии. Категорийное ревью по 12 осям с адверсариальной верификацией: 80 находок, **54 подтверждено, 16 правдоподобно, 10 опровергнуто**; после дедупа **46 уникальных дефектов** — 3 блокирующих класса, 15 HIGH, 17 MEDIUM. Батарея была зелёная, потому что все три блокирующих дефекта живут в путях, которые тесты проходили только в безопасной форме. **Починено в зоне** (регистр PD-257…PD-281): пере-нарезка книги больше не валит читающую модель навсегда (отложенный констрейнт, миграция 00018) · упавший `tmctl export` больше не затирает текст пустотой · весь блок идемпотентности (гонка первых попыток 500→409, область ключа, отпечаток с размером файла, закрытие ключа вне контекста запроса) · база полосы прогресса считает тот же проход, что числитель · SSE (подрезанные кадры → `resync_required`, дедлайн записи, id из будущего) · материализация не голодает свип · банк читается на штатном деплое · деплой-нота больше не включает обратно грант, который PD-104 выключил. Батарея: 18 пакетов, exit 0, скипов 0, линтер 0 issues, тестов **458 → 490**. ⚠ **Приёмка ПРАВОК (9 осей + fable-5) нашла, что восемь дефектов внесли сами правки** — батарея не увидела ни одного, три были запинены под ошибку автора. Все исправлены; разбор — хендофф §0. **Б-1 ПОЧИНЕН разбором с владельцем 17.08 (PD-277).** Рамка приёмки была неверна: это не «нужен новый канал в движок», а невыполненная половина строки единого бэклога **191(в)** (слово владельца 16.08, D39.144) — «проводка `resume = снятие стопа` до движка». Движок при выключенном `--verify-bank` уже делает модель владельца: неподписанное едет авто-строками с пометкой ⟨проверить⟩ (`mining.go:211-231`, D39.42 п.3), а платформа передавала флаг обратно и роняла прогон в тот же стоп. Снято: `l.VerifyBank && !l.BankReleased`; пин `TestAResumedRunIsSpawnedWithoutTheSigningStop`. ⚠ Остаток: `decline` пользователя до движка не доезжает (движок читает `mined_rejects`) — строка бэклога **192**, отложена владельцем. **PD-273 закрыт решением владельца 17.08:** статус остаётся `awaiting_bank`, лечение — признак «стоп был запрошен» в контракте; готовая строка для единого бэклога — хендофф §7(и), вставляет оркестратор (зона `docs/` не наша). **Разбор, план, обязательства через оркестратора и команды воспроизведения — `platform/docs/archive/P7_ACCEPTANCE_HANDOFF_2026-08-17.md`.** ⚠ Заявки раздела «Сессия P7» ниже приёмку НЕ прошли — читать их вместе с хендоффом, не вместо. - **P7 ПОСТРОЕН — заявка САМОЙ сессии написания (17.08).** ⚠ **Приёмку эта заявка НЕ прошла** — см. верхнюю запись и `archive/P7_ACCEPTANCE_HANDOFF_2026-08-17.md`: 46 уникальных дефектов, три блокирующих класса, из них ни один батарея не видела. Читать ниже как исходную позицию, а не как состояние дерева. Читающая поверхность контракта **0.3.0**: главы · пары с текстом · замечания · банк и подпись · SSE на КНИГЕ · `GET /capabilities` · машинная модель ошибок (`code` + `request_id`) · условные чтения и сжатие · `Idempotency-Key` · `structure_version`. Плюс форварды (`title`, `reject_reason`), PD-104 (грант → 0), демонтаж построенного гейта полноты банка (D39.144), PD-241, конформность построенного по §5 промта и рантбук деплоя, прогнанный ЖИВЫМ `migrate`. Батарея на стенде `~/.local/pgsql` (порт 55433) под `-race` с ОБОИМИ гейтами: **EXIT=0, 18 пакетов, скипов 0, линтер 0 issues**, тестовых функций **416 → 458**. Дифф: 46 файлов, +2329/−646. Селф-ревью финалом — ТРИ независимых ревьюера, два из них другой модели: **22 находки, 17 подтверждены и починены** (две HIGH нашли оба контрактных ревьюера независимо), 5 — диспозиция с аргументом. Разделы ниже — записка-план, живые пробы, таблица селф-ревью, obstacle, вопросы оркестратору. - **Запись оркестратора, 15.08 — P6 + ДОФИКС ПРИНЯТЫ И ЗАЛЕНДЕНЫ (D39.132).** Приёмка двумя раундами: панель 5 адверсариальных верификаторов по P6 (две линзы — другой моделью; исполнением, включая живой демон и живой PG) → фикс-лист ФП-1…ФП-8 → дофикс → финальная верификация исполнением: батарея `make check` под `-race` с живым PG и ОБОИМИ гейтами пере-прогнана оркестратором — EXIT=0, 17 пакетов, скипов 0; тестов **416** (`grep -rh '^func Test' platform --include='*_test.go' | wc -l`; HEAD = 359); деньги пере-считаны из сырого леджера стендовой БД двумя путями (сошлись до цента); живая проба рендера пере-снята своим tmctl из HEAD; 2 собственные посадки приёмки на дофикс — обе пойманы (`TestAFlaggedBookStillAnswersAboutItsMoney` · `TestEachOfTheThreeBlockersAloneKeepsABookOutOfTheMigrationList` — вторая до дофикса переживала всю батарею). **Исправлено оркестратором при лендинге:** откачено переименование закрытой строки PD-198 → PD-199 и удалена заведённая под PD-198 строка-дубль с ложным обоснованием (регистр 246 → 245 слиянием; «ID стабилен навсегда»); PD-200 перенесена в «Закрытые — эра P6», PD-180 — в «Открытые — info». Ратификации и диспозиция вопроса PD-241 — тело D39.132. - **ДОФИКС P6 ЗАКРЫТ — дерево передаётся на лендинг одним пакетом с P6 (15.08).** Фикс-лист приёмки ФП-1…ФП-8, раздел «Дофикс P6» ниже. Ни одна находка не опровергнута, но две подтвердились не так, как их описала приёмка. Самая тяжёлая — денежная и на ОБЫЧНОМ пути: движок отвечает exit 2 из `status --json` про любую книгу с помеченной единицей, а платформа читала это как «движок не ответил», из-за чего расчёт такого прогона откладывался вечно и следующий прогон книги не стартовал. Плюс аддендум владельца 15.08: `day_usd` убран из ПРИМЕРА шаблона книги. Батарея под `-race` с обоими гейтами: EXIT=0, 17 пакетов, скипов 0, линтер 0 issues, `make vuln` чист, **359 (HEAD) → 416** тестовых функций — счёт командами в разделе. Посадки 15 из 15. - **P6 ПОСТРОЕН — дерево передаётся на лендинг (14.08).** Потребительская половина шва эмиттера (П-15) · интейк формы Б (П-14) · дев-стенд с сидом и дев-входом (П-16) · порядок апгрейда движка против деадлока v15 · попутные PD · гигиена зонных доков. **PD-113 закрыт — открытых major в регистре НЕТ.** Батарея на стенде `~/.local/pgsql` (порт 55433) под `-race`: EXIT=0, 17 пакетов, линтер 0 issues, скипов 0, `make vuln` чист, тестов зоны **359 → 401** (числа P6; счёт — команды в разделе «Дофикс P6», прежний базис «396 → 403» не воспроизводился, PD-234). Посадки: 20 из 20 в первом круге (три переписаны после разбора переживших) + 6 из 6 на находки адверсариальной панели, одна названа НЕПИНЯЕМОЙ по построению. Живые пробы на настоящем `tmctl` и на фейке движка с правилом потолка — разделы ниже. ⚠ **Три вопроса на ратификацию оркестратору** (подробности в разделе P6): внутренняя причина `daily_ceiling` НЕ проецируется на провод (контракт знает одно значение — PD-199, спек-правка за S4) · направление `PLATFORM_DIRECTION` §3 предложено ПЕРЕ-ПОДПИСАТЬ (oapi-codegen и sqlc не исполнены, срок «до первого хендлера» пройден) · батарея получила ВТОРОЙ гейт окружения (`TM_PLATFORM_TEST_ENGINE_BIN` + `_BOOK_TEMPLATE`), без него один тест честно скипается. - **P5 ПОСТРОЕН и ДОФИКШЕН по приёмке — дерево передаётся на лендинг (11.08).** *(→ принят в три раунда и заленден D39.130, `69d485a` — запись оркестратора 14.08 ниже.)* `POST /v0/books` потоково с пер-маршрутным потолком тела и своим дедлайном чтения · статусы `uploading → parsing → not_started | rejected` получили писателей, разбор — $0-команда движка `tmctl manifest` · `POST /v0/runs/{id}/stop|resume` с намерением стопа в Postgres ДО сигнала · метрики Prometheus на отдельном слушателе · печать эффективной конфигурации. - **Приёмка 11.08 (8 находок) → дофикс → кросс-семейное ревью дофикса (5 находок) → ре-чек оркестратора (3 находки, 2 high) → ТРЕТИЙ РАУНД ЗАКРЫТ (14.08)**: разделы «Третий раунд», «Дофикс-2» и «Дофикс P5» ниже, движение — записками-планами. Батарея на стенде `~/.local/pgsql` (порт 55433) под `-race`: EXIT=0, 16 пакетов, линтер 0 issues, скипов 0, `make vuln` чист, тестов **261 → 359**. Посадки: 10/10 · 6/6 · 6/6 (две пережившие за все раунды — оба раза виноват был слепой пин, переписан пин, не мутация); списки — в журнале. ⚠ **Тулчейн 1.26.6 НА РАТИФИКАЦИЮ** *(→ ратифицирован D39.130)*, и вчерашний клейм про него был ЛОЖЕН: гейт принимал 1.26.5 (регекс вместо сравнения). Теперь floor держат два механизма — `make version-check` сравнивает версии, `go.mod` несёт `toolchain go1.26.6`, который читает всякая сборка; при `GOTOOLCHAIN=auto` хост скачает нужный тулчейн, при `=local` остановится. Само сравнение запинено (`internal/gates`). **ВОПРОС на ратификацию — кто пишет `book.yaml` при интейке**: половина П-9 стоит на нём, раздел «Развилка» ниже. *(→ решено D39.130: бета = форма Б, стройка = П-14; движковая форма В = строка 170 единого.)* - **Запись оркестратора, 14.08 — P5 ПРИНЯТ И ЗАЛЕНДЕН (`69d485a`, D39.130).** Финальная верификация исполнением: батарея EXIT=0 на том же стенде · **гейт версий живьём отказывает 1.26.5 и `go1.27rc1`, принимает 1.26.6 и 1.26.10** (sort -V честный) · `toolchain go1.26.6` в go.mod · сентинел `.tmplatform-books` на месте · `revision+1` остался ровно у двух заявленных прогресс-писателей синка (мотивированное исключение ратифицировано) · тестов **359**, регистр **198**, удалённых имён тестов против HEAD нет. **Ратифицировано (D39.130):** Go-floor 1.26.6 с toolchain-директивой (цена офлайн-хоста с `=local` названа и принята) · `book.yaml` при интейке — форма **Б** (рендер из деплой-шаблона; интерпретация D39.110 §2b с аудит-следом, стройка — следующим касанием зоны), форма **В** (`tmctl init`) — строка 170 единого бэклога движку · контракт-диспозиции PD-172/173/174/180 — спек-правкой 0.2.3 задачей S4-промта · PD-196 → дописка строки 165 (различимость классов отказа `manifest`). - **Пинг оркестратора, 14.08 (второй) — ЭМИТТЕР ДВИЖКА ЗАЛЕНДЕН (D39.131, `9cfe080`): `events.jsonl` начнёт появляться в каталогах книг.** Словарь = ваш `events.go` с диффом движка (принят приёмкой): `StreamVersion` **1.1** · `Ceiling.Scope: book|day` · `Finished.outcome += ceiling|stopped` · `eta_seconds` = темп ТЕКУЩЕГО прогона · `unit_done.unit` = лидерный `first_chunk_idx`. Ваша половина — **П-15** зонного бэклога (маппинг exit 4/5/10–19 · `unit_done` присваиванием · scope · dev-супервизор · порядок деплоя против деадлока v15 — строка 174 единого, РЕШИТЬ ДО деплоя нового бинаря). PD-113/PD-196 в регистре остаются open до П-15 — движковая половина построена, потребительская нет. - **Пинг оркестратора, 14.08 — гигиена зонных доков по аудит-свипу корпуса, закрыть следующим касанием зоны (вместе с П-14):** `README.md:85` «тулчейн ≥1.26.5» → 1.26.6 c toolchain-директивой · `README.md:87` «River не подключена — П-3» и `STACK_DECISIONS.md:23` «River в go.mod НЕ добавлен» — противоречат go.mod и построенной P4-очереди · `README.md:47-48` вопрос book.yaml → решён D39.130 (форма Б) · `PLATFORM_DIRECTION.md`: oapi-codegen «взять — доказано» против `ENGINEERING_STANDARDS:59` «кандидат при P1» — оба не исполнены (ручки P4/P5 рукописные), sqlc «до первого хендлера» не случился (PD-44 открыт), «River в go.mod не заводить» отстал — направление пере-подписать честно · `DEFECT_REGISTER.md`: PD-172/173/174/180 не несут диспозицию D39.130 (спек-правка 0.2.3 задачей S4) · открытые PD-180/185/196 живут в секциях «Закрытые…» · PD-178 стоит после PD-191 (порядок номеров) · PD-99 (fixed кодом) лежит в «Закрытых ратификацией» · путь `docs/scripts/counts.py` в шапке регистра исполним только от корня репо · веса `high`/`medium-high` секций дофиксов против объявленных major/minor/info · `STACK_DECISIONS.md:17` называет гейт «make tools-check», хотя `version-check` отделён (FP5-9) · П-1/П-3 зонного бэклога не знают построенного P4 (вес «Ф3, после контракта» отстал) · хвост (г) третьего раунда — перечислительность мета-пина systemd-гейта — носителя не имеет: завести PD-строку или закрыть явно. - **P4 «раннер» + дофикс + V2 ПРИНЯТЫ и ЗАЛЕНДЕНЫ — D39.123, код `d29e30c`** (ревью-шапка ниже): транзиентный systemd-юнит на прогон · очередь River с холдом-до-спавна в одной транзакции · реконсилятор с базовой линией расчёта · тейлер `events.jsonl` с карантином проекции · пять ручек `/v0` контракта **0.2.1** · дев-интейк. Батарея с живым PG 18.4 под `-race` зелёная, скипов 0, тестов зоны **261**. Формула аргумента потолка — `committed + прирост` (PD-158, ратифицирована ПОПРАВКОЙ к пингу оркестратора). - **Регистр — 245 строк** (`python3 docs/scripts/counts.py` от корня репо; было 246 — лендинг слил строку-дубль PD-198, испр. оркестратором №16 15.08): 178 закрыто · 1 закрыт ратификацией (PD-59) · 4 риском · **62 открыто (17 minor, 45 info), из них 0 major** — PD-113 закрыт P6 вместе с приходом эмиттера. Форма — 15 секций (добавлены эра P6 и дофикс P6). - **Промт P5 ВЫДАН оркестратором 10.08** (`PLATFORM_P5_SESSION_PROMPT.md`, D39.128): П-9 `POST /books` (+PD-72 потолок тела; развилка «кто пишет book.yaml при интейке» — вопросом в этот журнал ДО стройки той половины) · стоп/резюм-ручки PD-140 (различение «потолок vs авария» — за эмиттером движка, строки 103/165, его промт выдан параллельно) · наблюдаемость П-11 + печать конфигурации PD-114 · DEFECT_REGISTER секциями (пинг №16). Запуск — по слову владельца. *(→ исполнен сессией P5 11–14.08, принят D39.130; промт с баннером-исходом — `archive/PLATFORM_P5_SESSION_PROMPT_2026-08-10.md`.)* Эскроу/uncertain (строка 136) — следующим денежным промтом, НЕ в P5. - Эры P0–P3 (вход OIDC · кредиты · админ-CLI · деплой · фикс-паки) — исполнены и залендены (D39.107/109/112/114); разделы — в [archive/platform-PROGRESS-P0-P3.md](archive/platform-PROGRESS-P0-P3.md). Стек и рецепт стенда — `STACK_DECISIONS.md`; критерии приёмки — `ENGINEERING_STANDARDS.md`. *(Записи паков P4–P6 с дофиксами (08–15.08) — в [archive/platform-PROGRESS-P4-P6.md](archive/platform-PROGRESS-P4-P6.md); вынесено 16.08 по слову владельца.)* ## Эра пака P7 (16–20.08) — в архиве Пять актов, приёмка, фикс-раунды и таблицы селф-ревью выселены срезом в [`archive/platform-PROGRESS-P7.md`](archive/platform-PROGRESS-P7.md) (норма: закрытая эра не живёт в журнале). Итог и то, что пережило пак, — шапка выше и пинг оркестратора №18 ниже. ## Закрытые эры P0–P3 — в архиве Разделы сессий P0–P3 и их ратификаций (04–08.08) вынесены в [archive/platform-PROGRESS-P0-P3.md](archive/platform-PROGRESS-P0-P3.md) (D39.124). Решения оттуда живут в D-логе и `DEFECT_REGISTER.md`. ## Пинги оркестратора (живые ссылки для следующих сессий) **Пинг оркестратора №15 (09.08, D39.122): движковый пак «блокеры контракта» ПРИНЯТ и заленден `0e69bc1` — пять поверхностей для платформы существуют. ФИНАЛЬНЫЕ формы (менялись трижды за приёмку — старые в переписке игнорировать):** - **Манифест глав:** `.manifest.json`, `manifest_version: "tm-manifest-v2"` (v1 движок сам отклоняет); id главы = 16 hex (стабилен через пере-нарезку); **`unit.id = ::`** — тег разреза 8 hex, при любой смене нарезки/данных пары unit.id УМИРАЮТ намеренно (id жив ⇒ якорь цел); поле `heading` — ВРЕМЕННЫЙ рендер движка «Глава N», НЕ метка книги (решение владельца 09.08; настоящие заголовки — строка 160 бэклога движка). $0-команда `tmctl manifest` строит дерево до первого прогона (первое касание создаёт БД проекта — то же поведение, что у status). - **Прогресс:** `status --json` → `progress: {draft:{done,total}, edit:{done,total}}` на книге и в каждом элементе `chapters`; `done` = «разрешено волной» (ok/flagged/skipped); волна, которой нет, — `total: 0`; процент не отгружается — собирает клиент. - **Банк:** `.bank.json` (весь банк тремя статусами; id термов — длино-префиксированный хеш ключа уникальности, стабилен через пересборку) и `.bank-stop.json` (полная таблица подписи; `conf: null` ≠ 0). ⚠ **ИСПРАВЛЕНО оркестратором №18 (20.08, линза шва P7): «ВСЕ сайдкары атомарно» — НЕВЕРНО.** Атомарны (temp+`Sync`+rename, `backend/internal/pipeline/artifact.go:24-59`) ровно три — `manifest.json`, `bank.json`, `bank-stop.json`, — и читать во время прогона можно ИХ. `bank-stop.txt`, `mined-signature.yaml`, `auto-bank.yaml` пишутся `os.WriteFile` с усечением первым делом: читателей у них сегодня нет, и сессия, взявшая любой по прежней формулировке, получила бы усечённый документ на живом прогоне. - **Потолок (строка 145): `tmctl translate|redrive --ceiling-usd ` — это КНИЖНЫЙ ПОТОЛОК В СИЛЕ, не бюджет прогона** (ратифицировано D39.122): леджер сравнивает значение с накопленным committed+reserved книги. Пересчёт пользовательского «прирост в главах» (D39.110) в абсолют — ОБЯЗАННОСТЬ платформы. **⚠ АМЕНДИРОВАНО D39.123 (PD-158): формула = `committed_usd + прирост×оценка`, БЕЗ reserved** — read-only `status` показывает leftover-reserved, который `store.Open` зануляет до первой судимой резервации; включение переплачивало бы запасом сверх холда (исполнено обеими формулами против гейта). `reserved_usd` читается обязательным полем как гард присутствия и улика leftover. Значение ниже уже потраченного откажет первой же резервации. День-потолок не перекрывается. Опция по желанию зоны: движок готов провести `--ceiling-usd` и в `status`, чтобы мониторинг видел действующий потолок capped-прогона (сегодня status показывает книжный) — скажите, заведём строку. ## Пинг оркестратора №16 — 09.08.2026 (форма DEFECT_REGISTER, строка 167) Реестр дефектов дорос до 171 строки одной таблицей; докс-аудит D39.125 (worksheet `docs/archive/reports/DOC_AUDIT_INVENTORY_2026-08-09.md`, жалоба «зонная изоляция прячет половины тем») просит форму: разложить таблицу на СЕКЦИИ (open по весу · accepted-risk · fixed по эрам паков), сохранив построчную форму `| PD-N | … |` — `docs/scripts/counts.py` ключуется формой строки, не позицией, секции его не ломают. Заведите П-строкой в свой BACKLOG, исполнение — попутно следующим паком, НЕ срочно. Вопросы — через владельца. ## Пинг оркестратора №17 — 15.08.2026 (migrate принят, D39.134 — движковая половина самолечения ФИНАЛЬНА) Бэкенд-пак `tmctl migrate` принят и заленден (`d55edd4`): exit 13 = `schema_mismatch` финализирован, токен `schema_mismatch found=N expected=M` на stderr стабилен, write-путь тоже отказывает БД новее бинаря. Ваш PD-201 (самолечение «поймал 13 → migrate → повтор») теперь можно строить — движок больше не сдвинется под ногами; строку реестра, ссылающуюся на «движкового migrate ещё нет», обновите. Хвосты вашей зоны, найденные приёмкой (вход промта P7, исполнение попутно): (1) `deploy/README.md:131` цитирует МЁРТВЫЙ текст ошибки схемы («schema vN … expects vM») — движок его больше не печатает; (2) та же мёртвая цитата и уплывший якорь `store.go:135-143` в строке П-1 вашего BACKLOG; (3) комментарий-образец в `tmplatformctl` (`runs.go:45`) зовёт голый `tmctl` из PATH — копипаст воспроизводит тихий no-op СТАРЫМ бинарём при живом деадлоке, ваш же README:139-141 требует версионированный путь. Вопросы — через владельца. ## Пинг оркестратора №17 (второй) — 15.08.2026 (S4 принят, контракт 0.2.3 в каноне — пять строк вашего регистра протухли; аудит корпуса) Фронт S4 принят и заленден (D39.135), контракт **0.2.3** в каноне. Для вашего регистра: (1) **PD-172 · PD-173 · PD-174 · PD-180 — диспозиция «спек-правка 0.2.3 задачей S4» (D39.130 п.2в) ИСПОЛНЕНА** — правила стоят в `openapi.yaml` §createBook (PD-172 при этом уточнён ПО ВАШЕМУ коду: чтение останавливается на файле, обязательное после файла = 400 «как не слали», необязательное молча теряется — D39.135 п.2б); обновите статусы. (2) **PD-199 ратифицирован закрытым ещё D39.132 п.2а** («null на проводе подтверждён»), в регистре до сих пор open — привести. (3) **Ваше НОВОЕ обязательство в P7** (D39.135 п.2в): читать `BookIntake.title` (сегодня падает в «unknown field is IGNORED», `v0.go:369-376`) и проецировать `books.reject_reason` на провод (сегодня «kept for an operator and never projected», `v0.go:497`) — без этого 0.2.3 остаётся обещанием без носителя. (4) Кандидаты в P7 по аудиту корпуса 15.08: **PD-219** (упавший дрейн теряет хвост `unit_resolutions` навсегда — зона сама вешала его на читающую поверхность) · **PD-217** (книга на вечном холде блокирует апгрейд движка — вторая половина после D39.132) · **PD-162** (удалённый каталог книги = вечный прогон с открытым холдом). (5) ⚠ Ваш же журнал честно фиксирует: «`tmctl migrate` живьём не гонялся» — команда теперь СУЩЕСТВУЕТ и принята (D39.134); мнение оркестратора: прогнать деплой-рантбук end-to-end на дев-стенде настоящим migrate — обязательное предусловие первого выката, дешевле любого пака. Вопросы — через владельца. ## Пинг оркестратора №17 (третий) — 16.08.2026 (контракт-ревью принято D39.138: ваша половина батча 0.3.0, PD-104 закрыт, попутные находки) Контракт-ревью API v0 отработало отдельной сессией и ПРИНЯТО (отчёт — `docs/research/28-contract-review.md`; решения владельца — его §8, ратификация D39.138). ⚠ Читать ОРИГИНАЛ отчёта, не этот пересказ — он и есть носитель. 1. **PD-104 ЗАКРЫТ словом владельца 16.08** (§8 п.14): `SignupGrantMicroUSD` → **0** на бете, начисление руками; возврат $5 — вместе с суточным агрегатным потолком, когда появятся платежи. Исполнение — P7-однострочник конфига + тест (грант при неверифицированном email уже 0, `login.go:306-308`). Обновите регистр. 2. **Ваша половина батча 0.3.0 — ПОСЛЕ лендинга спеки; состав — §5 отчёта:** `wireProgress` → один счётчик (Б-0; ваши колонки и словарь шва НЕ трогаются — фазы остаются внутренним делом); словарь машинных `code` + `request_id` в `Problem` (Б-1/§8а: класс 1 — конкретика максимальная, класс 2 модельный — ОДИН грубый код без вариации); `GET /capabilities` (Б-2: пары/порог интейка/форматы/страницы/версия — всё уже есть в конфиге и константах); `WWW-Authenticate` на 401 и две причины 403 (Б-15); трейлинг-части интейка → 400 вместо тихой потери + `Location` на 201 + `Idempotency-Key` (Б-3); подрезание `limit` до максимума вместо сброса к дефолту (Б-10, `books.go:509-511`); `PATCH /books/{id}` title-only + `DELETE` + `GET /runs/{id}` + `Run.failure_reason` (Б-5/Б-8 — словарь исхода у движка уже есть); `blocked: {code, book_id}` на run-options/409 (В-6); отказ по неподдерживаемой паре кодом на интейке (сегодня ja/en-книга умирает `failed`-ом на валидации конфига движка в КОНЦЕ пути — денег не сгорает, но причина не доезжает). 3. **Сеть (§5б; вход до первой живой книги под фронтом, строка 186):** gzip на текстовых ответах (НЕ на SSE) и `ETag`/`If-None-Match`→304 на списочных GET — ваше же требование `PLATFORM_DIRECTION.md:202-205`, батчем оно уезжает В КОНТРАКТ. Числа: дерево 2283 глав = 250 КБ на кадр `status`, фокус-рефетч 12 вкладок = 562 КБ. 4. **К-10 (пофазность у главы): НЕ СТРОИТЬ** — поправка приёмки: вердикт §6 отчёта («правка проекции») противоречит Б-0; фазы уходят с провода и у главы. 5. **Попутные находки §9** (сессия их адверсариально НЕ судила — проверьте у себя, заведите П/PD-строки по месту): (а) расчёт может висеть навсегда в двух легаси-путях (`reconcile.go:660-670` — попытка без базовой отметки; дев-путь без движка) — бюджета «сколько холд может висеть» не существует; (б) флаг «аккаунт остановлен» выводится сканом последних прогонов ВСЕХ книг (`pgstore/books.go:720-724`) — одна книга зажигает аккаунт; после PD-203 перечитать; (в) `ReadRunForSpawn` линеен по живым прогонам (`pgstore/runs.go:712`); (г) `/metrics` без аутентификации, защита — только 127.0.0.1 (`main.go:200-202`); (д) лимитер входа один на процесс (`login.go:128-129,165`) — один клиент упирает вход всем (выбор объяснён комментарием, следствие стоит записать); (е) сырой Go-текст ошибки лежит причиной карантина в БД (`reconcile.go:320`) — на провод не идёт. Вопросы — через владельца. **Аддендум к пингу №17-третьему (16.08, свип планировочных доков D39.139):** (1) `docs/PLATFORM_DIRECTION.md` §2 и `BACKLOG.md` П-7 держат «фри-тир дефолт $5» — ПРОТУХЛО против PD-104 (слово владельца 16.08: грант 0 на бете, начисление руками; D39.138 п.2л) — поправить тексты при P7. (2) Баннер §3 PLATFORM_DIRECTION («ратификация за оркестратором») закрыт ещё D39.132 п.2б: oapi-codegen — кандидат при P7, sqlc — привязан к P7, River-факт поправлен; баннер снять/заменить ссылкой на ноту. (3) §4 п.5 («сервер ВПРАВЕ склеивать события») сузится батчем 0.3.0: склейка только снимкам, `note` не склеивать (research/28 Б-6в). Вопросы — через владельца. **Дозакладка к §4.6 промта P7 (16.08, аудит готовности зоны; релей владельца — подтверди эхом):** к списку протухших текстов зоны добавь: `PLATFORM_DIRECTION.md` §4 п.8 («живого канала до-прогонных состояний нет») — решено книжным SSE 0.3.0, опрос запрещён нормой · §4 п.10 («персист манифеста — недостающий») — построен D39.122 · `:184` ссылка на несуществующий §3.3 стандартов · `platform/README.md:3-4` шапка «зона на P1» и «один тест-гейт» (гейта два) · `BACKLOG.md` П-17 — состав пака у́же промта (промт первичен). Расхождение ENGINEERING_STANDARDS:59 против DIRECTION:128 по oapi-codegen решаешь ты (промт §4.6). Всё — попутно, не отдельным заходом. ## Пинг оркестратора №18 — 20.08.2026 (P7 акт 5 ПРИНЯТ С ФИКС-ЛИСТОМ и ЗАЛЕНДЕН; контракт 0.4.0 ратифицирован; тело — D39.152/D39.153) **Вердикт: ПРИНЯТЬ С ФИКС-ЛИСТОМ.** Панель шести линз в изолированных копиях (слепая · контракт-конформность · деньги · шов · вне карты · ревью канона 0.4.0 другой моделью) + пере-раны и собственные посадки оркестратора. Регрессов против HEAD нет, ни одной линзы с REJECT. **Пере-проверено МОЕЙ рукой (заявление = команда):** `make check` с обоими гейтами — 18 пакетов, EXIT=0, **скипов 0**, линтер 0 issues · реестр 327 строк / 65 открытых (13 minor, 52 info), major и BLOCKER нет — `python3 docs/scripts/counts.py --check` · оба док-гейта зелёные · `tmplatformctl books --migratable` живой (exit 0, «resumable run» на стендовой книге) · миграционный манифест сходится, released-миграции не тронуты, комментарий про пере-отпечаток 00016 честный. **Замер, обосновавший разворот 00016→00022, пере-выведен независимо** (свой бенчмарк на трёх вариантах в копии дерева): джойн **14.6 мс** · счётчик **5.3 мс** · без счётчика **2.9 мс**. Порядок и вывод подтверждаются, разворот законен. ⚠ Процент назван в ТРЁХ носителях тремя разными числами (журнал «83%», `perf_test.go:13` «96%», миграция 00022 «16.8 против 3.4, джойн сам по себе 9 мс»); мой замер даёт ≈80%, а «96%» ни из чего не выводится — один носитель на факт, остальные указывают. **Собственные посадки мутаций ВНЕ вашего списка — 8, поймано 7:** долг не поставлен третьей концовкой ✅ · `edit_wave` не монотонен ✅ (⚠ сообщение пина печатает указатель вместо значения — `readmodel_test.go:918`) · снят `Vary: Accept-Encoding` ✅ · HEAD лишён валидатора ✅ · `reopen` снимает стоп банка кому угодно ✅ · погашение долга без сверки метки ✅ · вынос метки долга из закрывающей транзакции во второй оператор ✅ (техника `xmin` §36 работает, проверено исполнением). ⚠ **НЕ поймано — дыра:** снятие `structure_version` И `revision` из `emitFrame` (`pgstore/events.go:72-73`) проходит ВСЮ батарею. Это два поля, которые канон требует на КАЖДОМ кадре (`EventBase`), и ваш же комментарий это утверждает. По вашей норме PD-1 свойство без пинящего теста считается НЕ закрытым. ### Фикс-лист приёмки — завести строками СВОЕГО бэклога и регистра (зона моя не пишет) ⚠ **ПЕРВЫМ — не из пака P7, но найдено вторым рубежом приёмки и проверено мной построчно: `Sweep` останавливается для ВСЕЙ инсталляции на двух медленных прогонах, и выхода нет.** `cmd/tmplatformd/runner.go:189` — `sweepBudget = 2 * time.Minute` на ВЕСЬ проход; `internal/runs/reconcile.go:70` — `defaultRunBudget = 60 * time.Second` на ОДИН прогон. 120/60 = 2: два прогона, выбравшие свой бюджет, съедают проход целиком, и тогда `ctx.Err()` (`reconcile.go:39,48`) обрывает цикл — **`UnsettledRuns`, единственный ретрай отложенного расчёта, не вызывается вообще**, а живые прогоны, стоящие ниже, не реконсилируются и не спавнятся. `ListLiveRuns` сортирует `order by r.started_at` (`pgstore/runs.go:249`), то есть заклиненный прогон — по построению самый старый — стоит в голове и голодит остальных КАЖДЫЙ проход, детерминированно. Ручки нет: `runs.Config.RunBudget` объявлен (`internal/runs/runs.go:62-65`), но `startRunner` его НЕ присваивает (`cmd/tmplatformd/runner.go:74-83`), переменной окружения нет ни для одного из двух чисел, а `tmplatformctl` не умеет ни закрыть прогон, ни вернуть холд. Цена: прогон вечно `finished_at is null` ⇒ холд не возвращается ⇒ `runs_one_live_per_book` не даёт запустить новый прогон этой книги. Деньги заморожены, книга заморожена, пользователю видно «идёт». **Достижимо буднично, без экзотики:** `RunSink.Apply` берёт транзакцию с блокировкой книги на КАЖДУЮ строку журнала (`pgstore/sink.go:80-104`), поэтому после часа простоя демона две книги с бэклогом выбирают проход целиком — и всё время догона деньги всей инсталляции не считаются. ⚠ **`PD-169` в вашем регистре стоит `fixed(P5)` и этим ЛЖЁТ приёмке:** его пин гоняет `Sweep` вообще без дедлайна прохода, то есть доказывает пер-прогонный бюджет, а не выживание прохода. Пере-открыть. Код приехал в P6 и в дифф P7 не входил (`Sweep` против HEAD — identical), поэтому ни одна диффовая линза его увидеть не могла: тот же класс «композиция известных фактов», что и строка 198. ⚠ Наблюдаемость при этом ЕСТЬ и она ваша: `tm_platform_sweep_unfinished_total` растёт — то есть оператор увидит, что проход не дошёл до конца, и не сможет ничего сделать. Метрика без ручки — половина механизма. 1. **`ContractVersion` = "0.3.0" при формах 0.4.0** (`internal/httpapi/capabilities.go:13`, он же в каждом `hello` — `stream.go:100`). Ратификация 0.4.0 состоялась (D39.152) ⇒ константа обязана подняться. Комментарий над ней сам это требует: «raised in the same commit as the code that implements a new minor». Сегодня практического вреда нет (фронт заморожен на 0.2.3), но деплой объявляет версию, которую не отдаёт. **Первым пунктом.** 2. **Пины на два поля `EventBase`** (см. выше). ⚠ Попутно 21.08 усилен гейт якорей доков: `file:line` теперь может нести токен ожидания (`` `путь:12-14`=`подстрока` ``), и такой якорь сверяется ПО СОДЕРЖИМОМУ на каждом прогоне, включая коммиты, которые двигают ЦЕЛЬ. Ваши доки (`platform/docs/**`) вошли в область линта — раньше их не сканировал никто. 3. **Бюджет попыток материализации.** У долга нет предела и канала «признать безнадёжным»: claim → fail → defer(`now()`) → следующий свип через 15 с, вечно (`pgstore/books.go:538,557-564`). У интейка предел есть (`parseAttempts`=5). Три следствия: до 5 мин движковых процессов на проход бесконечно · поток такой книги НЕ заканчивается никогда (`AtRest` требует `read_model_owed_at is null`, `events.go:125`) · при живом манифесте и падающем экспорте `SaveStructure` коммитится каждый проход ⇒ `revision++` и кадр каждые 15 с. 4. **sqlc — БЕРЁМ, слово владельца 20.08 («я вообще за»).** Отступление P7 закрыто, PD-44 переоткрыть исполнением. ⚠ Ваше предложение «взять на однооператорных ручках P8» покупает инструмент туда, где не болит: рантайм-ошибки «нет такой колонки» случились в СКЛЕЕННОМ SQL read-модели (⚠ счёт: проверяемый носитель — ваш регистр PD-44 и журнал — несёт ДВЕ; ещё две вы назвали в ответе владельцу, в доки они не попали, поэтому опираемся на две — испр. 20.08) (`chapters_before`/`stop_for_signing` — `readmodel.go:441,446`, там же `note_count` и сломанный алиас `b`), куда sqlc по построению не дойдёт. Тем же паком — гейт, который туда дойдёт: прогонять КАЖДЫЙ собранный запрос через разбор Postgres (prepare/describe) против мигрированной схемы; склейка ему не мешает, он получает финальную строку. И ответить попутно на вопрос, которого мы не знаем: **есть ли в read-модели запрос, которого не касается ни один тест** — если есть, это не «медленная обратная связь», а дыра. ⚠ **Половина ответа УЖЕ получена вторым рубежом приёмки** (собранный SQL извлечён из пакета через `go/types` и прогнан `EXPLAIN (GENERIC_PLAN)` против мигрированной схемы): **141 запрос, все планируются чисто, остаточных «нет такой колонки» НЕТ**; тестом ЭТОГО пакета недостижимы десять, но по батарее ЦЕЛИКОМ реально не покрыты только **три** (`DeleteOldLoginEvents` · `UserByIdentity` · `Observe`; остальные семь покрыты на 58–87% тестами соседних пакетов — пере-мерено покрытием при аудите 20.08, первая редакция называла десять и это было неверно) — `RefundParseAttempt` · `StuckIntake` · `OpenReservations` · `ReleaseUnspawned` · `DeleteOldLoginEvents` · `UserByIdentity` · `Observe` · `SpendBound` · `AttemptReservationOpen` · `RunPausedReason`, и ни один из них не трогает схему 00016–00024. То есть дыры сегодня нет, а гейт нужен как ПОСТОЯННЫЙ — именно он и делает этот ответ воспроизводимым. 5. **PD-297** — round-trip на строку в `SaveStructure` под эксклюзивной блокировкой книги; мерить на корпусной книге до и после (корпусная книга появится на холодном прогоне движка). 6. **Труба доставки решений банка в движок** — единый бэклог, строка **199(а)**: перед resume писать решения в `mined_delta`/`mined_rejects`. Сегодня `action`/`dst` — write-only колонки (единственный SELECT `readmodel.go:336` проверяет лишь наличие строки), а воркер описан в комментарии вашей же миграции `00002_readmodel.sql:172-174` и не построен. 7. **Мусор:** пустой `platform/ru` (0 байт, обрубок редиректа) — в лендинг не взят, снесён оркестратором. `books.go:234-235` — задвоенная первая строка доккомментария, класс PD-310/326. 8. **Ревью-пак четырёх осей, которых не смотрел НИКТО** (ваш же obstacle): деньги и леджер целиком · вход/сессии/CSRF · очередь и джобы · метрики. Это не дофикс и не довесок — первый взгляд, отдельной работой. 9. **Один носитель на факт для замера 00016→00022 — но НЕ удалением числа.** Выигрыш назван ЧЕТЫРЬМЯ носителями: шапка этого журнала «83%» · `internal/pgstore/perf_test.go:13`=`96% of the page` «96%» · миграция 00022 «16.8 против 3.4, джойн сам 9 мс» · регистр PD-306 (повторяет 83% и 16.8/3.4). ⚠ Я сперва записал, что «96%» ни из чего не выводится — **это была моя ошибка, снята проверкой записей**: оно выводится точно из ВАШЕГО же замера в `archive/P7_ACT5_FIX_PLAN_2026-08-20.md:444-446`=`636 мс против` («636 мс против 24 мс», холодный корпус акта 4) = 96.2%, тогда как 83% и мой независимый ≈80% — с вакуумированного корпуса. То есть носители меряли РАЗНОЕ и все честны. Свести указанием УСЛОВИЙ замера при каждом числе, оставив нормативным один (миграция 00022 — она их и несёт); удалять «96%» как фантом НЕЛЬЗЯ. 10. **У материализатора нет ПОЛА на пустой манифест — латентная потеря всего текста книги.** `internal/readmodel/readmodel.go:215-232` строит `in.Chapters` только из `manifest.Chapters` и ни разу не сверяется со счётчиками того же документа (`ChaptersTotal`/`UnitsTotal` лежат рядом и печатаются в лог строкой ниже). Пустой список едет в `SaveStructure`, где `pgstore/readmodel.go:193-196` выполняет `delete from chapters where book_id = $1 and not (id = any($2))` — на пустом массиве предикат истинен для ВСЕХ глав, и каскад `chapters → units` сносит текст; при пустом `Key` вдобавок срабатывает `changed` и уходят все `unit_resolutions` (родня строки 198). Сегодняшним движком недостижимо (`buildManifest` всегда наполняет главы, файл пишется атомарно) ⇒ фикс-лист, не блокер. **Асимметрия и есть находка:** на ИНТЕЙКЕ ровно этот случай отловлен явно и прибит мутацией (`internal/books/parse.go:134-142`, `books_test.go:1296`) — «нет глав, но работа считается» там не признаётся правдой о книге. У материализатора такого пола нет, и теста на нулевой манифест в `readmodel_test.go` тоже нет. Лечится одной сверкой `len(manifest.Chapters)` против `manifest.ChaptersTotal`. 11. **`PD-327` в регистре стоит `open`, а канон 0.4.0 РАТИФИЦИРОВАН** (D39.152) — то есть условие её закрытия наступило. Закрыть строку регистра явно; сегодня расхождение видно скриптом (`counts.py --check` печатает PD-327 среди открытых), и это ровно тот класс, ради которого регистр объявлен источником истины по статусу. 12. **Изоляция читающих чтений НЕ ЗАПИНЕНА (аудит 21.08, посадка мутации).** Снятие `RepeatableRead`+`ReadOnly` у `inReadTx` (`internal/pgstore/credits.go:492-493`) проходит ВСЮ батарею — при том, что под этим инвариантом лежат шесть ручек выдачи, а носитель прямо объясняет цену («every frame in that window was lost for good», `books.go:684-688`, PD-163). По вашей же норме PD-1 свойство без пинящего теста считается НЕ закрытым — а это тот самый класс, которым я мерил найденную дыру с `emitFrame`. Код приехал НОВЫМ в P7, то есть лежал внутри диффа, который читали шесть линз приёмки и я сам. 13. **`CreditHeldBy`: оговорка `and book_id <> $2` не запинена (аудит 21.08, посадка мутации).** Мутант, снимающий её, проходит батарею: пин, который выглядит покрывающим, её ни разу не исполняет — в фикстуре у исключаемой книги холда НЕТ (`internal/pgstore/credits_test.go:443-465`). Сумма кормит контрактный `blocked`, и раздутая сумма отправляет пользователя гасить прогон, который ничего не освободит — ровно тот вред, против которого написан комментарий над самим запросом (`credits.go:393-397`). ### Что ушло в ЕДИНЫЙ бэклог (движок/шов/контракт — ваши строки туда не заходят, D39.84) **198** апгрейд движка стирает замечания и счётчики безвозвратно (ваша зачистка при ре-кате × announce-once движка — композиция, гейт холодного прогона) · **199** канал доставки правок банка · **200** сквозная полоса прогресса вместо пофазной (слово владельца 20.08) · **201** «Глава N» внутри текста экспорта · **202** живой прогон насквозь через API — ПОСЛЕ холодного прогона движка (слово владельца 20.08) · **203** хвосты контракта · **204** движок публикует причины флагов данными (релей §7в, ваш PD-246 — строка заведена в бэклоге ДВИЖКА, как вы и просили). ⚠ По релеям §7 сверено грепом при лендинге, а не по вашему списку: **(б) и (д) уже ИСПОЛНЕНЫ** синком 0.4.0, **(г) наполовину** (`unspecified` ратифицирован, открыта граница ступеней), **(и)** закрыт полем `stop_requested`. Реально открыты только (а)-канон-половина, (з) и договорная часть (к). ### Ратификации, которые вас касаются **Контракт 0.4.0 РАТИФИЦИРОВАН (D39.152)** — PD-327 закрывается лендингом канона, он состоялся. ⚠ Ваш клейм «ломающая правка ровно одна» верен для диффа генерённых ТИПОВ и неточен поведенчески: против 0.3.0 расходятся ПЯТЬ мест — пятое, семантика `410 Gone`, принесена проверкой записей и названа вашим же PD-253 (`stop_requested` · тождество интейка по содержимому против дословного «never over the bytes themselves» · `409` там, где таблица резюма говорит `202` · валидатор на двух ручках сверх объявленных). Все четыре 0.4.0 благословляет, поэтому цена уплачена ратификацией — но клейм в отчёте стоит поправить, чтобы следующая приёмка не опёрлась на него. **Оговорка про пер-термные решения расширена на ВСЮ ручку** (была только на `decline`): инертны одинаково и `approve`, и `dst`. Оговорка временная — снимается исполнением строки 199(а). ⚠ **Исправление МОЕГО же пинга №15 (испр. оркестратором №18):** «Все сайдкары пишутся атомарно (temp+rename)» — НЕВЕРНО. Атомарны три из шести (`manifest.json`, `bank.json`, `bank-stop.json` — `backend/internal/pipeline/artifact.go:24-59`); `bank-stop.txt`, `mined-signature.yaml`, `auto-bank.yaml` пишутся `os.WriteFile` с усечением. Сегодня безвредно (читателей нет), но сессия, взявшая любой из них по той строке, получит усечённый документ на живом прогоне. **Сессия P7 ЗАКРЫТА владельцем 20.08.** Промт отработан → `platform/docs/archive/`. Дальше — другие сессии.