# Слайс зонного журнала: эра пака P8-FIX (21–22.08.2026) > ⚠ **Архив. Инструкции отсюда НЕ исполнять, при онбординге НЕ читать** — только по конкретной ссылке. > Вынесено оркестратором №18 22.08 при актуализации доков. Состояние зоны — шапка живого > `../platform-PROGRESS.md`; дефекты — `../DEFECT_REGISTER.md`; правила, пережившие пак, — > `../STACK_DECISIONS.md`; ратификация — **D39.154** в `docs/architecture/05-decisions-log.md`. > > **Что этот пак сделал:** закрыл фикс-лист приёмки P7 (пункты 1–7 и 9–13) и врезанный первым > блокер свипа `PD-169` — голодание, из-за которого расчёт денег вставал для ВСЕЙ инсталляции. > Плюс спил пер-термного write-пути банка по слову владельца (`PD-370`, ратификация D39.144). > Лендинг — `31f1f82`. > > ⚠ **Что из этого отчёта ПЕРЕНЕСЕНО в долговечные носители и там первично:** > инвентарь каналов шва с движком → `../STACK_DECISIONS.md` §«Инвентарь каналов движка» (и там он > ПОЛНЕЕ: дописан канал текста `tmctl export --json --pairs`, которого в этой таблице не было) · > развилка владения `book.yaml` с четырьмя вариантами → строка **199(а)** единого бэклога · > поимённый разбор канона под минор 0.5.0 → строка **206** · граница sqlc (41 запрос) → `BACKLOG.md` > **П-19** · ловушка `.check.log` и третий гейт батареи → `PD-374`. - **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(а) выше |