Final documentation pass in the platform zone: keep what a session needs to work, drop the account of how we got there, and correct the stale claims about code
| П-16 | **ИСПОЛНЕНО P6 (14.08).** Дев-сид стенда: тестовый пользователь, чтобы фронт разрабатывался на живой платформе, а не на своих моках (слово владельца 14.08). Границы: фикстуры фронта остаются батарее его гейтов (детерминированные состояния — не работа стенда); полный снос `frontend/src/mock/` — триггер Ф-29, после читающей поверхности | ИСПОЛНЕНО P6: `tmplatformctl seed` и дев-вход `TM_PLATFORM_DEV_LOGIN`с четырьмя гардами непроходимости в проде — рецепт и разбор гардов в `deploy/README.md` §«Дев-стенд». Прогнано на стенде целиком | владелец 14.08 |
| П-17 | **ИСПОЛНЕНО P7 (16–17.08).** Читающая поверхность — остаток П-1, названный отдельной строкой | ИСПОЛНЕНО P7 по канону 0.3.0; состав поверхности — `platform/README.md` §«Что здесь будет» и §«Карта зоны» (`internal/readmodel`). ⚠ `submitBankDecisions` СНЯТ 22.08 вместе с пер-термной моделью подписи — D39.144, слово владельца; см. PD-370. **НЕ вошло и отложено в P8:**`updateBook`/`deleteBook`/`getRun`, экспорт (`createExport`/`getExport`), эскроу (П-18) | П-1, D39.84/85, контракт 14 |
| П-18 | **Эскроу денег шва: write-ahead intent · `uncertain` · `closing`** (строка 136 единого бэклога) — денежный промт, сознательно НЕ взятый ни P5, ни P6. Здесь же закрывается PD-154 (`settled_at` между `Settle` и `MarkSettled`): половина эскроу рядом с проектируемым целым — второй, более слабый ответ на тот же вопрос | денежный промт | строка 136, PD-154 |
| П-19 | **Конверсия свободного от склейки блока под `sqlc`**(решение владельца 20.08 «БЕРЁМ»; граница названа P8-FIX, PD-44). Пять файлов без единой склейки — `credits.go`(15) · `identity.go`(13) · `idempotency.go`(7) · `sessions.go`(5) · `observe.go`(1) = **42 места / 41 текст / 40 КОНВЕРТИРУЕМЫХ** (⚠ испр. D39.172:`observe.go` структурно не конвертируется — River мигрирует `river_job` сам; худший позиционный дрейф набора, семь `int64` подряд, остался рукописным): это весь кусок пакета, где инструмент силён и ничего не ломает. Остальные 25 склеенных мест недостижимы по построению (`lastRun` — девять потребителей, `nextRevisionOfThisBooksLibrary` — восемь), и это ровно та часть, где рантайм-ошибки и случались. Состав пака: `sqlc.yaml` + пин версии инструмента (как у`golangci-lint`) + генерённый код в дереве + гейт «сгенерённое актуально» + `WithTx` для одиннадцати денежных запросов, идущих внутри ЧУЖОЙ транзакции. ⚠ Класс «нет такой колонки» УЖЕ закрыт постоянным гейтом (`TestEverySQLStatementParsesAgainstTheMigratedSchema`, 162 оператора, включая склеенные), поэтому этот пак покупает типизированные скан-структуры и раннюю обратную связь, а не корректность. **РЕШЕНО владельцем 22.08: ОТДЕЛЬНОЙ СЕССИЕЙ, не внутри пака** — изменений много, мешать их с чем-либо нельзя | отдельная сессия | PD-44, фикс-лист приёмки P7 п. 4, решение владельца 22.08 |
| П-19 | **Конверсия свободного от склейки блока под `sqlc`**— ⚠ **ИСПОЛНЕНО отдельной сессией 29.08 и заленджено (D39.172; `PD-44` закрыт).** Отдельной сессией, а не внутри пака, — решение владельца 22.08: изменений много, мешать их с чем-либо нельзя. Что построено (пин v1.31.1, `sqlc.yaml`, генерённый код в дереве, два гейта актуальности, override `*.*_micro_usd` на деньги) — `docs/STACK_DECISIONS.md`, строка «Кодоген SQL». ⚠ Живая ГРАНИЦА набора, которую стоит помнить при следующем касании: **42 места вызова / 41 текст SQL / 40 конвертируемых**;`observe.go` структурно не конвертируется — River мигрирует `river_job` сам, и внесение чужой схемы в конфиг завело бы ВТОРОЙ её носитель (испр. D39.172), поэтому худший позиционный дрейф набора — семь `int64` подряд — остался рукописным. Остальные 25 склеенных мест недостижимы по построению (`lastRun` — девять потребителей, `nextRevisionOfThisBooksLibrary` — восемь), и это ровно та часть, где рантайм-ошибки и случались. ⚠ Пак покупал типизированные скан-структуры и раннюю обратную связь, аНЕ корректность: класс «нет такой колонки» закрыт постоянным гейтом `TestEverySQLStatementParsesAgainstTheMigratedSchema` (пол — 140 операторов; после конверсии видит 172 — числа в `PD-44`) | ИСПОЛНЕНО (D39.172) | PD-44, фикс-лист приёмки P7 п. 4, решения владельца 20 и 22.08 |
| П-20 | **Хвост PD-162 после P8-FIX: холд прогона, чей движок не ответит НИКОГДА, возвращается только рукой.** Пак дал оператору и диагноз (`tmplatformctl runs --stalled`, гейдж `tm_platform_runs_stalled`), и терминальный вердикт (`run abandon [--release-hold]`), и это сознательная граница: автоматически решать про деньги прогона, о котором нельзя спросить, — та же ошибка «я не смог спросить = его нет», только со счётчиком впереди. Автоматический ответ на этот вопрос и есть эскроу — write-ahead intent · `uncertain` · `closing` (строка 136 единого бэклога, П-18). Пока эскроу нет, ручка остаётся ручной, и это записано, а не подразумевается | вместе с П-18 | P8-FIX |
| П-21 | **Ключ идемпотентности под конкуренцией даёт 500 (PD-369).** ⚠ Не работа пака P8-FIX и файл им не тронут — найдено попутно, прогоном батареи: собственный тест `TestAClaimThatLostARaceToAReleaseIsRetriedAndNotAnError` ФЛЕЙКОВЫЙ, 1–2 падения на ~80 прогонов. Причина не в тесте: `ClaimIdempotency` ретраит проигранную гонку ровно `claimRounds = 3` раза, а восемь одновременных попыток одного ключа, каждая из которых сразу отдаёт ключ назад (как делает любой 4xx), могут отобрать гонку у одного проигравшего трижды подряд — и он получает внутреннюю ошибку вместо одного из четырёх контрактных ответов. Направление, а не решение: граница по ВРЕМЕНИ вместо числа раундов либо `ErrKeyInFlight` при исчерпании — это решение о контрактном поведении | до первого чужого пользователя | попутная находка сессии P8-FIX |
| PD-201 | bug | info | `deploy/README.md`, движок `tmctl migrate` | **Самолечение деплой-деадлока v15 ЖДЁТ движковую половину.** Read-only `status` отказывает файлу проекта старее бинаря, платформа зовёт его перед каждым спавном и на расчёте денег — значит выкат движка запирает книги до `tmctl migrate`. Порядок апгрейда записан и исполним (`tmplatformctl books --migratable` пропускает книги с живыми прогонами, резюмируемыми попытками и незакрытыми холдами — все три блокируют по РАЗНЫМ причинам, аддендум оркестратора 14.08). Чего нет: движковый `migrate` придёт с машиноразличимой ошибкой «версия не совпала», и тогда платформа сможет лечиться сама — поймала → `migrate` (если у книги нет открытых попыток) → повтор. Вслепую не строится: без формы ошибки любой матчер был бы догадкой по тексту. ⚠ Проверка исполнением самого шага `migrate` тоже ждёт и НЕ помечена сделанной ⚠ **ПОЛОВИНА ЗАКРЫТА P7 (проверка исполнением):** рантбук деплоя прогнан end-to-end на стенде живым `migrate` — проектная БД, отведённая на схему v14 при бинаре v15, даёт `tmctl status --json` exit 13 с токеном `schema_mismatch found=14 expected=15`; `tmctl migrate` делает пред-миграционный бэкап и переводит v14 → v15; тот же `status` после — exit 0. Мёртвая цитата ошибки вычищена из `deploy/README.md` и из П-1 бэклога, пример в `tmplatformctl/runs.go` переписан на ВЕРСИОНИРОВАННЫЙ путь бинаря. ⚠ ОСТАЁТСЯ сам автомат самолечения («поймал 13 → migrate → повтор») — он не построен: строить его правильно значит решать, кто имеет право мигрировать книгу с открытыми попытками, а это тот же вопрос, что у`books --migratable` | open | аддендум оркестратора 14.08 (ресёрч деплоя) |
| PD-204 | standards | info | `internal/runner/systemd_test.go` мета-пин | **Мета-пин systemd-гейта ПЕРЕЧИСЛЯЕТ формы отказа вместо того, чтобы спрашивать способность.** Хвост (г) третьего раунда, у которого не было носителя. Сам гейт вылечен (PD-178: спрашивает, доходит ли процесс до своего менеджера), а тест, который его сторожит, по-прежнему знает список сообщений — у стража та же болезнь, от которой лечили охраняемого. Принято как ОСТАТОКс названной ценой: systemd меняет тексты между версиями, и мета-пин протухнет молча; вреда сегодня нет, потому что он не гейт, а страж гейта. Заведён по пингу оркестратора 14.08 | open | третий раунд P5, хвост (г) |
| PD-205 | hardening | info | `internal/login/dev.go``login`, `internal/auth/csrf.go``cookieUnsafe` | **Дев-вход защищён от login-CSRF только ОДНИМ слоем из двух.**`auth.CSRF` требует заголовок `X-TM-Client` лишь на unsafe-запросе, который УЖЕ несёт сессионную куку, а на входе куки по определению нет — значит остаётся только `http.CrossOriginProtection`, и клиент, не присылающий ни `Sec-Fetch-Site`, ни `Origin` (тот самый браузер до 2023, ради которого второй слой и заведён), может кросс-сайтом ввести браузер жертвы в ДЕВ-аккаунт. Последствие на стенде ничтожно — аккаунт один и общий, — но свойство слабее, чем «POST, значит безопасно», и записано, а не подразумевается. ⚠ Тот же класс у боевого `/auth/login` (он вообще GET) и по той же причине; лечится либо требованием заголовка на login-маршрутах, либо явным принятием | open | адверсариальное ревью P6 (кросс-семейное, Fable) |
| PD-212 | bug | info | движок `cmd/tmctl/main.go``exitCode`, `internal/runs/reconcile.go``outcome` | **Непойманная паника движка неотличима от «завершено с флагами».** Go-рантайм завершает процесс с кодом РОВНО 2 на непойманной панике, а 2 — это `CompletedWithFlags`, единственный «успешный» код контракта; `recover` в `cmd/tmctl`/`internal/pipeline` отсутствует (грепнуто ревьюером). Платформа читает только `$EXIT_CODE`/`$EXIT_STATUS` и записывает `ready` для прогона, который упал посреди работы. Деньги целы (расчёт берёт цифру из `status --json`), врёт статус. Лечится НЕ здесь: либо `recover` в main движка, либо другой номер для флагов — запрос уходит строкой единого бэклога через оркестратора. ⚠ Платформа МОГЛА бы различить по отсутствию терминальной строки `finished` в журнале, но сознательно не судит прогон по строке, которую крэш обрезает | open | адверсариальное ревью P6 (линза шва) |
| PD-212 | bug | info | движок `cmd/tmctl/main.go``exitCode`, `internal/runs/reconcile.go``outcome` | **Непойманная паника движка неотличима от «завершено с флагами».** Go-рантайм завершает процесс с кодом РОВНО 2 на непойманной панике, а 2 — это `CompletedWithFlags`, единственный «успешный» код контракта; `recover` в `cmd/tmctl`/`internal/pipeline` отсутствует (грепнуто ревьюером). Платформа читает только `$EXIT_CODE`/`$EXIT_STATUS` и записывает `ready` для прогона, который упал посреди работы. Деньги целы (расчёт берёт цифру из `status --json`), врёт статус. Лечится НЕ здесь: либо `recover` в main движка, либо другой номер для флагов — запрос уходит строкой единого бэклога через оркестратора. ⚠ Платформа МОГЛА бы различить по отсутствию терминальной строки `finished` в журнале, но сознательно не судит прогон по строке, которую крэш обрезает ⚠⚠ **ПРОТУХЛА 02.09, чиню фактом: ДВИЖОК ЭТО СДЕЛАЛ, и посылка строки («`recover` отсутствует») больше не верна.**`backend/cmd/tmctl/main.go` несёт `exitOf` — `main` идёт под `recover`, паника заворачивается в `obs.NewPanicError` и `exitCode` проверяет `*obs.PanicError`**ПЕРВЫМ**, отдавая **1**, а не 2 (греп `func exitOf` и `case errors.As(err, &panicked)`); волновые воркеры несут свой `recover` на шве (греп `recover()` в `backend/internal/pipeline/waverun.go`). Пины движка — `backend/cmd/tmctl/panic_exit_test.go`; лендинг `f840867`. То есть «паника читается как завершено-с-флагами» больше не воспроизводится. **Статус не перевожу — закрытие есть акт лендинга и лекарство в ЧУЖОЙ зоне**; оркестратору: строка готова к закрытию, либо к пере-формулировке в остаток (различение крэша по обрезанному журналу платформа по-прежнему не строит и строить не собирается) | open | адверсариальное ревью P6 (линза шва) |
| PD-214 | bug | info | `internal/ingest/tail.go``apply` | **Чужая или битая hello-строка в общем журнале книги гасит материализацию СВОЕГО потока.** Версия, непустой `engine_run_id` и `seq == 1` проверяются для ЛЮБОЙ hello-строки ДО того, как код решает, чья она: ветка «not mine» стоит после них. Значит чужой процесс (ручной `tmctl` оператора в каталоге книги, старый мажор, баг чужой сборки) валит `Tail` ошибкой, а`quarantines()` считает её терминальной — проекция здорового платящего прогона уходит в карантин НАВСЕГДА (снятия карантина в дереве нет), свежесть падает на медленный ре-синк. Данные целы, деньги целы. Лечится порядком: при известном `want` чужой id распознаётся ДО валидации хендшейка | open | адверсариальное ревью P6 (линза шва) |
| PD-215 | bug | info | `internal/runs/reconcile.go``restart`, `interruptedBySomeoneElse` | **У петли перезапусков нет ни счётчика, ни бэк-оффа.** Каждый цикл — строка `run_attempts`, три строки леджера (холд/возврат/расчёт), два вызова `tmctl status` и транзиентный юнит. Источник, который на каждой попытке тратит ~0 (движок, мгновенно падающий по внешней причине), крутит это вечно: `remaining` не убывает, значит `exhausted` никогда не наступает. Денег не теряется, но это неограниченная работа и рост таблиц. Класс существовал и до пака (путь ребута, строка 138), а ветка «exit 5 без намерения = прерывание» его РАСШИРИЛА. Лечить счётчиком перезапусков на прогон или бэк-оффом по времени последней попытки | open | адверсариальное ревью P6 (линза денег и гонок) |
| PD-216 | bug | info | `internal/runs/reconcile.go``outcome` (полоса отказов) | **Exit 12 (`project_locked`) закрывает прогон терминально, хотя это единственный класс полосы, который проходит САМ.** Обоснование «отказ воспроизводим по построению, поэтому перезапуск — петля» верно для 10/11/19 и неверно для лока: другой процесс отпустит проект. Денег не теряется (холд возвращается целиком, списывается 0), но прогон убит и пользователь покупает новый. Не исправлено намеренно: перезапуск на 12 без бэк-оффа — это PD-215 в чистом виде, поэтому оба лечатся вместе | open | адверсариальное ревью P6 (линза денег и гонок) |
> колонках, которых нельзя получить из кода платформы: **атомарность записи** (какие сайдкары можно
> читать на живом прогоне, а какие рвутся) и **какие каналы движка платформа не потребляет вовсе**.
> ⚠ Якоря в таблице — на код ДВИЖКА, он живёт своей жизнью: при расхождении первичен код.
> ⚠ **Канал `<project_db>.bank-stop.json` СНЯТ из таблицы 02.09: его больше нет.** Файл и его
> писатель снесены вместе с пер-термной моделью подписи (**D39.158**); проверено
> `grep -rc 'bank-stop.json' backend/ --include='*.go'` → ноль вхождений. Читателя в зоне у него
> не было никогда, так что ничего платформенного на нём не стояло.
| Канал | Писатель в движке | Атомарность | Читатель/писатель платформы | Согласовано? |
|---|---|---|---|---|
| `<project_db>.manifest.json` | `pipeline/manifest.go``writeFileAtomic` | **атомарно** (temp+`Sync`+rename, `pipeline/artifact.go`) | `internal/runner/engine.go` (`tmctl manifest`) → `ingest.DecodeManifest` → `internal/books/parse.go`, `internal/readmodel` | ✅ и с P8-FIX строже: `ingest.Manifest.Whole()` зеркалит `BookManifest.selfConsistent` движка по тем правилам, которые платформа читает (главы, юниты, нумерация). Верхняя граница чанков движка counterpart'а не имеет НАМЕРЕННО — платформа счётчиков чанков не берёт |
| `<project_db>.bank-stop.json` | `pipeline/mining.go``writeFileAtomic` | **атомарно** | **читателя НЕТ** (грепом по зоне — ноль вхождений) | ⚠ канал существует и не потребляется: полная таблица подписи платформе сегодня не нужна, она проецирует банк |
| `<project_db>.bank-stop.txt` | `pipeline/mining.go``os.WriteFile` | **НЕ атомарно** (усечение первым делом) | читателя нет | ✅ **и это важное правило, а не наблюдение: брать его на ЖИВОМ прогоне нельзя** — прочитаешь обрезанный файл без всякой ошибки |
| `<project_db>.mined-signature.yaml` | `pipeline/mining.go``os.WriteFile` | **НЕ атомарно** | читателя нет | ✅ то же правило |
| `<project_db>.auto-bank.yaml` | `pipeline/mining.go``os.WriteFile` | **НЕ атомарно** | читателя нет | ✅ то же правило |
| `<project_db>.bank-stop.txt` | `pipeline/mining.go``os.WriteFile` (греп `bankStopTablePath`) | **НЕ атомарно** (усечение первым делом) | читателя нет | ✅ **и это важное правило, а не наблюдение: брать его на ЖИВОМ прогоне нельзя** — прочитаешь обрезанный файл без всякой ошибки |
| `<project_db>.mined-signature.yaml` | `pipeline/mining.go``writeFileAtomic` (греп `signatureMapPath`) | **атомарно** | читателя нет | ✅ ⚠ испр. 02.09: писатель сменился на атомарный, прежняя строка «`os.WriteFile`, НЕ атомарно» была протухшей |
| `<project_db>.auto-bank.yaml` | `pipeline/mining.go``writeFileAtomic` (греп `autoBankPath`) | **атомарно** | читателя нет | ✅ то же исправление |
| `mined_delta` (путь из `book.yaml`) | **писателя в движке НЕТ** — только читатель `loadMinedDelta`; формат `seed.File` (`terms:`), грузится `membank.LoadGlossarySeed`, `Source` пере-штампуется на `"mined"` | — | **писателя НЕТ и у платформы** | ❌ **разрыв — это строка 199(а) единого бэклога**, развилка ждёт ратификации |
| `mined_rejects` | читатель `loadMinedRejects`; формат `rejects: [{src, note}]` — это ФИЛЬТР ПРЕДЛОЖЕНИЙ, в банк не входит | — | писателя нет | ❌ тот же разрыв |
| `tmctl bank-apply` + документ решений | зона ПИШЕТ (`internal/runs/bank.go``decisionsFile` → файл во временном каталоге), движок отвечает отчётом `tm-bank-report-v1` на stdout | документ пишется целиком до вызова; отчёт — одноразовая выдача на вызов | `internal/runner/bankapply.go` → `ingest.DecodeBankReport` → `internal/runs/bank.go``bankVerdict` | ⚠ ЕДИНСТВЕННЫЙ канал, по которому платформа ПИШЕТ в проект движка, — отсюда и своя дверь (`POST /books/{bookId}/bank/corrections`), и свой класс отказов, и класс `write_incomplete` = exit 15. ⚠ Пост-verb факт этого канала (`bank_moved_at`) пишется на ОТДЕЛЬНОМ, отцепленном контексте — иначе обрыв клиента теряет его навсегда (`PD-425`) |
номерных якорей в файле теперь **ноль**. В сам комментарий вписано, почему по символам.
**5 (minor, обязательно сейчас) — миграция 00030 неверно цитировала D39.163.** Ссылка была на
«словарь стадий закрыт, третьего исключения нет»; D39.163 про то, что ВТОРОЕ исключение выдано под
двумя условиями. Переписано по существу: границу канона (§Boundaries) непрозрачному счётчику
поколения исключение не нужно вовсе, потому он им и является. Хеш в `migrations.sha256` пере-записан.
**8 (minor, три места).** `readmodel.go` «small and bounded» ушло вместе со старым комментарием при
п.1. `platform/README.md`: добавлен СЕДЬМОЙ канал (`tmctl bank-apply` — единственный, которым зона
@ -173,15 +181,12 @@ reads as coverage» — и сам её не исполняет до конца:
### Числа дофиксов (каждое — командой)
- `make check` при трёх гейтах + условии хоста: **18 пакетов, EXIT=0, скипов 0**, линтер «0 issues»,
`sqlc diff` чист.
- `counts.py --check`: битая форма пусто, хвост вне словаря пусто; открытых **97** (major 2 · minor 32 ·
info 63).
- `counts.py --lint`: **0 проблемных якорей в 101 живом доке.** ⚠ По дороге пришлось пере-нацелить ещё
ДВА — их убила моя же правка `deploy/README.md` из п.8; правило «якорь, убитый твоим переездом,
чинишь ты» сработало на мне в тот же заход.
- Новых посадок: **M20**, **M21** (обе на правило п.1), обе красные адресно.
- Новый бенчмарк `BenchmarkChapterPage`: 0.617 мс против 0.496 мс до джойна.
- Тестов после дофиксов: **+12 новых, 3 пере-подписаны** (дифф `^func Test` исполнением + новые файлы).
`sqlc diff` чист; `counts.py --check` и `--lint` чисты.
- Новый бенчмарк `BenchmarkChapterPage`: **0.617 мс против 0.496 мс** до джойна (страница 100 глав,
вакуумированный корпус) — цена одного правила вместо двух.
- ⚠ Урок, стоивший времени: пере-нацелить пришлось ещё ДВА якоря, убитых моей же правкой
`deploy/README.md` из п.8 — правило «якорь, убитый твоим переездом, чинишь ты» сработало на мне
в тот же заход.
## ПАК P12 ОТРАБОТАН — семь major разобраны, обход `--verify-bank` снят и пробит живьём, канон 0.9.0; адверсариальный проход нашёл в СВОЕЙ работе три регрессии и они откачены (сессия `textmachine-main-b5`, 31.08)
@ -189,27 +194,15 @@ reads as coverage» — и сам её не исполняет до конца:
| Уязвимости | `make vuln` | `No vulnerabilities found`; `go.mod`/`go.sum`НЕ изменились — sqlc это тул, не зависимость |
| Ни один тест не исчез | `git grep -hoE '^func (Test\|Fuzz\|Benchmark)\w*' HEAD` против дерева | **638 → 643** против HEAD `72434ce`; дифф — РОВНО пять моих добавлений, строк `<` нет вовсе: ни потерь, ни переименований |
| Гейт актуальности РАБОТАЕТ | правка `queries/credits.sql` без регенерации | `sqlc diff` exit **2**; восстановление — exit 0 |
| Якоря реестра | `python3 docs/scripts/counts.py --lint` | **0 проблемных**: четыре якоря, сдвинутые моими правками, пере-нацелены — `PD-377`/`PD-397`/`PD-394` сдвигом на ±1–2 строки, а`PD-380` ПЕРЕ-НАЦЕЛЕН, а не сдвинут (токен `s.pool.Exec` исчез вовсе, SQL уехал в `queries/sessions.sql`; новая цель — строка, где `maxAge` СТАНОВИТСЯ значением, причина записана в самой строке реестра) |
Батарея при всех четырёх гейтах стенда: 18 пакетов, EXIT=0, скипов 0, линтер 0 issues, `sqlc diff`
чист · `make vuln` чист, и `go.mod`/`go.sum`НЕ изменились — sqlc это тул, а не зависимость ·
перепись `^func (Test|Fuzz|Benchmark)` против HEAD `72434ce`: **638 → 643**, строк `<` нет вовсе,
то есть ни один тест не исчез и не переименован · гейт актуальности проверен нарушением: правка
`queries/credits.sql` без регенерации даёт `sqlc diff` exit **2**.
### Что пак КУПИЛ — посадки на конвертированном дереве
@ -541,12 +514,6 @@ Override с явным именем алиаса тоже НЕ применяе
собственный тест.** Починено строкой, вставленной прямо в SQL с РАЗНЫМИ значениями (700000/900000);
после этого посадка красная с точным диагнозом. Класс D39.171, найден только исполнением.
### Новые тесты (закрывают то, что пак ИЗМЕРИЛ как непроверенное)
Каждый пере-проверен посадкой — все четыре краснеют адресно.
### Адверсариальный проход по готовой работе (обязателен, слово владельца) — семь находок, все починены
Отдельный агент по шести осям, на готовом дереве, с правом мутировать копии. **Саму конверсию сломать
@ -587,19 +554,12 @@ Override с явным именем алиаса тоже НЕ применяе
фиксированному расписанию). Комментарий при этом утверждал, что класс закрыт. **Утверждение в
комментарии — это тоже заявление, и оно требует той же проверки исполнением, что и число в отчёте.**
### Вопросы оркестратору
### Вопросы оркестратору — все три ЗАКРЫТЫ позже
1. **`Touch` (`sessions.go:53`) — дефект БЕЗ строки регистра, и пак его не закрывает.** Сделать
`:execrows`с проверкой было бы изменением ПОВЕДЕНИЯ (сейчас ноль строк — норма), а не конверсией,
поэтому молча не взяла. Заводить строку или чинить отдельным паком?
2. **`observe.go` остался вне набора** — это уменьшает обещанный П-19 выигрыш на худшем случае набора.
Правка бэклога твоя: П-19 говорит «41 запрос» и `sessions.go`(5), фактически 42 места / 41 текст /
40 конвертируемых.
3. Ещё две дыры того же класса, найденные агентом и НЕ закрытые паком: `identity.go` `on conflict
(provider, subject) do nothing` — ветка `errIdentityRace` мертва целиком, то есть ON CONFLICT не
проверяет никто; и `PD-423`**не воспроизвёлся** — `cut -d: -f3 /proc/self/cgroup` даёт `/init.scope`,
а`TestARunIsBoundedByItsOwnCgroup` зелёный в трёх прогонах подряд у меня и у агента. Симптом строки
стоит пере-проверить.
`Touch` без проверки `RowsAffected` заведён `PD-431` и закрыт паком P12 сквозным пином через
`auth.Authenticator` · `observe.go` вне набора записан в `BACKLOG.md` П-19 живой границей (42 места /
41 текст / 40 конвертируемых) · мёртвая ветвь `errIdentityRace` в `identity.go` несёт открытую строку
`PD-433`, а`PD-423` получил третью точку от P12 и остаётся открытым без установленного диагноза.
## ПОСЛЕ ЛЕНДИНГА (2): аудит документации зоны — семь позиций разобраны, комментарий шва исправлен (сессия `textmachine-c0`, 29.08)
@ -612,29 +572,24 @@ Override с явным именем алиаса тоже НЕ применяе
берётся СЕГОДНЯ — гейт проводки вместе с`--max-units`. Вторая половина важнее первой, и довод его:
неверный ДОВОД сессия перепроверит, неверное ОЖИДАНИЕ она примет как карту.
**⛔ Что было ложью в доках зоны:**
1. `README.md` объявлял, что «ручки поправить/добавить термин нет ни в зоне, ни в каноне» — дверь
`POST /books/{bookId}/bank/corrections` построена паком P9 и смонтирована. Предложение склеивало
снятую ПЕР-ТЕРМНУЮ ПОДПИСЬ с правкой термина. Исправлено.
2. Там же «Три канала движка, и других нет» — и тут же перечислялись ПЯТЬ. Счёт правился, слово нет.
3. **Число условий батареи жило в ТРЁХ файлах и они расходились** (README — два, `ENGINEERING_STANDARDS` — три,
`STACK_DECISIONS` — четыре). Это самый дешёвый способ получить ложную приёмку: сессия, честно
исполнившая §3.1 по устаревшей копии, объявит «скипов ноль» при красном тесте. **Носитель теперь
ОДИН** — `STACK_DECISIONS`, «Гейты батареи»; две другие копии заменены ссылкой на него.
4. `STACK_DECISIONS` в лечебной команде самой частой грабли стенда указывал на несуществующий срез
`tm.slice` (в коде `runner.go` — `tm-runs.slice`), причём двумя абзацами выше тот же файл писал
правильное имя. Оператор получал «No such file or directory».
5. `deploy/README.md` держал жёсткое правило «эмиттер не выкатывается, пока `tmctl migrate` не
заленден». Глагол существует и заленджен (`backend/cmd/tmctl/migrate.go`, свой код выхода 13).
Правило блокировало выкат по несуществующей причине — худший род блокировки, снаружи неотличимый
от осторожности.
**⛔ Что было ложью в доках зоны — семь мест, все исправлены в самих доках.** Пять из семи —
устаревшее отрицание при построенном механизме (`README.md`: «ручки правки термина нет» при
смонтированной двери P9; «три канала движка, и других нет» при пяти в том же перечне;
`deploy/README.md`: запрет выката эмиттера «пока `tmctl migrate` не заленден» — глагол существует
и заленджен; `STACK_DECISIONS`: лечебная команда указывала на несуществующий срез `tm.slice` при
правильном `tm-runs.slice` двумя абзацами выше; `PLATFORM_DIRECTION`: таблица говорила «кодоген —
ВЗЯТЬ, доказано 05.08» при баннере «НЕ БРАТЬ» — замер доказал, что инструмент РАБОТАЕТ, а не что
его берут). Два оставшихся стоили бы дороже прочих и потому названы полностью:
3. **Число условий батареи жило в ТРЁХ файлах и они расходились** (README — два,
`ENGINEERING_STANDARDS` — три, `STACK_DECISIONS` — четыре). Это самый дешёвый способ получить
ложную приёмку: сессия, честно исполнившая §3.1 по устаревшей копии, объявит «скипов ноль» при
красном тесте. **Носитель теперь ОДИН** — `STACK_DECISIONS`, «Гейты батареи»; две другие копии
заменены ссылкой на него.
6. **`deploy/README.md` показывал боевой env, при котором инстанс не запустит НИ ОДНОГО перевода:**
нет `ENGINE_BIN`, `CTL_BIN`, `STATE_DIR` (дефолт вне `ReadWritePaths=` при `ProtectSystem=strict`)
и `ENGINE_KEYS_PATH` — единственного канала ключей. Каждый ОПЛАЧЕННЫЙ прогон падал бы `exit 10`, а
на буте это WARN, то есть тихо. Дописаны все четыре, с объяснением, почему каждая обязательна.
7. `PLATFORM_DIRECTION.md` нёс в таблице «кодоген — **ВЗЯТЬ**, доказано 05.08» при баннере того же
файла «решение P7 по обоим — НЕ БРАТЬ». Замер 05.08 верен и остаётся: он доказал, что инструмент
РАБОТАЕТ, а не что его берут. Слово вердикта было прочитано из замера.
**⛔ И самое неприятное — в РЕГИСТРЕ, и половина этого моя.** Секция и статус разошлись у десяти
строк: девять `fixed` и один `accepted-risk` лежали под заголовками «Открытые», то есть человек,
@ -696,46 +651,33 @@ $0.005460). Накладные масштабируются КНИГОЙ, а н
### Числа сдачи (§4.1)
⚠ **Два разных дерева, и я называю оба, потому что путать их — обычный способ соврать зеленью.**
Сдаваемое дерево (`ContractVersion` 0.8.0) при трёх гейтах: **17 пакетов ok, 838 PASS, скипов 0,
1 FAIL** — `TestARunIsBoundedByItsOwnCgroup`; на том же дереве до последних четырёх фиксов и на
базовой линии HEAD `fbe6cf3` — **18 пакетов, EXIT=0, скипов 0**. Линтер `0 issues`.
| дерево | команда | результат |
|---|---|---|
| **этот пак** (сдаваемое дерево, `ContractVersion` 0.8.0), три гейта | `go test ./... -race -count=1 -v` при `TM_PLATFORM_TEST_DSN` + `_ENGINE_BIN` + `_BOOK_TEMPLATE` | **17 пакетов ok, 838 PASS, скипов 0, 1 FAIL** — `TestARunIsBoundedByItsOwnCgroup` (`~/tm-p11/logs/FINAL2-verbose.log`; тот же результат и в `FINAL-verbose.log` до последних четырёх фиксов). ⚠ Не мой дифф и не флейк: см. ниже |
| тот же прогон на дереве в 02:04, до последних правок | та же команда | **18 пакетов, EXIT=0, скипов 0, 836 PASS** (`~/tm-p11/logs/final4-verbose.log`) |
| базовая линия HEAD `fbe6cf3` до правок, три гейта | та же команда | 18 пакетов, EXIT=0, скипов 0 (`~/tm-p11/logs/baseline2.log`) |
| **этот пак БЕЗ гейтов** | `env -u TM_PLATFORM_TEST_DSN -u …_ENGINE_BIN -u …_BOOK_TEMPLATE go test ./... -count=1 -v` | **EXIT=0 и 304 скипа** (`~/tm-p11/logs/final-nodsn.log`) |
| HEAD без гейтов | та же команда | EXIT=0 и **287** скипов (`~/tm-p11/logs/baseline-nodsn.log`) |
⛔ **ЕДИНСТВЕННЫЙ КРАСНЫЙ, и я НЕ выдаю его за зелёный.** Пакет `internal/runner` мой дифф не
касается вовсе, на том же коде тест был зелен в трёх предыдущих полных батареях и затем пять раз
подряд красен В ИЗОЛЯЦИИ — то есть не флейк и не нагрузка. Причина найдена ВНЕ Go и вне батареи:
`systemd-run --user --scope -p MemoryMax=64M …` на этом хосте даёт процессу спокойно занять 400 МиБ
и выйти с кодом 0. **Тест ПРАВ**: он ловит ровно то, ради чего написан, — что потолок памяти
прогона на этом хосте иллюзорен. Разбор, уточнение механизма приёмкой и ДВЕ последующие точки,
которые ему противоречат, — строкой `PD-423`, она единственный носитель этого сюжета.
⛔ **ЕДИНСТВЕННЫЙ КРАСНЫЙ, и я НЕ выдаю его за зелёный.**`internal/runner`
`TestARunIsBoundedByItsOwnCgroup` падает на финальном прогоне. Что установлено ИСПОЛНЕНИЕМ, а не
рассуждением:
- пакет `internal/runner` мой дифф не касается вовсе (`git diff --stat` по нему пуст);
- на ТОМ ЖЕ коде тест был зелен в трёх предыдущих полных батареях (`baseline2`, `final2`, `final4`) и
затем **пять раз подряд красен в изоляции** — то есть это не флейк и не нагрузка;
- причина найдена ВНЕ Go и вне батареи: `systemd-run --user --scope -p MemoryMax=64M …` на этом
хосте даёт процессу спокойно занять **400 МиБ** и выйти с кодом 0. Потолок памяти не применяется
самим systemd, `Delegate=no` и `MemoryMax=infinity` на `tm-runs.slice`.
- **Тест ПРАВ**, и его сообщение точное: он ловит ровно то, ради чего написан, — что потолок памяти
прогона на этом хосте иллюзорен.
⚠ **И моя собственная ошибка вывода, которую это вскрыло.** Я записала этот тест во «флейки под
параллельной нагрузкой» на основании СОВПАДЕНИЯ, а не замера; серийный прогон опроверг. `PD-420`
переписана только про `TestAClaimThatLostARaceToAReleaseIsRetried…`, а условие хоста вынесено
отдельной строкой `PD-423`.
⚠ **И моя собственная ошибка вывода, которую это вскрыло.** Ранее я записала этот тест во «флейки под
параллельной нагрузкой» — на основании того, что он краснел в чистых копиях кампании. Это было
выведено из СОВПАДЕНИЯ, а не измерено; серийный прогон опровергает. Строка регистра исправлена:
`PD-420` теперь только про `TestAClaimThatLostARaceToAReleaseIsRetried…` (он серийно зелен трижды
подряд — пере-проверено), а условие хоста вынесено отдельной строкой `PD-423`. Заодно это ЧЕТВЁРТОЕ
условие батареи, которого рецепт не называет.
⚠ **«Три гейта» здесь — это ТРИ ПЕРЕМЕННЫЕ** (`_TEST_DSN` · `_TEST_ENGINE_BIN` ·
`_TEST_BOOK_TEMPLATE`), а`STACK_DECISIONS` считает гейтом УСЛОВИЕ и потому даёт свою тройку.
Числа сходятся, потому что третье условие на этом хосте выполнено, — но счёт разный, и путать их
не надо.
⚠ **«Три гейта» здесь — это ТРИ ПЕРЕМЕННЫЕ** (`_TEST_DSN` · `_TEST_ENGINE_BIN` · `_TEST_BOOK_TEMPLATE`),
а`STACK_DECISIONS` считает гейтом УСЛОВИЕ и потому даёт свою тройку: DSN · пара ENGINE_BIN+TEMPLATE ·
достижимый пользовательский менеджер systemd. Числа сходятся, потому что третье условие на этом хосте
выполнено (`/run/user/1000` жив) — но счёт разный, и путать их не надо.
Скипы считаны командой (`grep -cE '^\s*--- SKIP'`), а не глазами, и НА ОБОИХ деревьях отдельно:
первая редакция этой таблицы писала «287, любое из двух», а это неправда — пак добавил пины,
гейченные тем же DSN, так что его собственное число 304. Разница 17 — ровно те новые тесты, которые
без Postgres не идут. Зелень без переменных не значит ни одного из этих чисел. ⚠ И правило, которое я забрала у соседней сессии и считаю более
общим: **зелёная батарея — это ПОЛНЫЙ СПИСОК ПАКЕТОВ плюс отсутствие FAIL, а не отсутствие FAIL.**
⚠ Скипы считаны командой (`grep -cE '^\s*--- SKIP'`) и НА ОБОИХ деревьях отдельно: первая редакция
писала «287, любое из двух», а это неправда — пак добавил 17 пинов, гейченных тем же DSN, значит
его собственное число 304 (оба числа живут в `STACK_DECISIONS`, «Гейты батареи»). ⚠ И правило,
которое я забрала у соседней сессии и считаю более общим: **зелёная батарея — это ПОЛНЫЙ СПИСОК
ПАКЕТОВ плюс отсутствие FAIL, а не отсутствие FAIL.**
### Что доказано ЖИВЬЁМ
@ -794,18 +736,25 @@ $0.005460). Накладные масштабируются КНИГОЙ, а н
Дословно — `~/tm-p11/probes/pd385-before.txt` и `pd385-after.txt`.
### Таблица комплектности против §3 (§6): строка → что сделано → каким ИСПОЛНЕНИЕМ подтверждено
### Семь строк заказа — что построено (доказательства ниже, по разделам)
| строка | что сделано | подтверждено ИСПОЛНЕНИЕМ |
|---|---|---|
| **`PD-379`** (vuln, major) | Личность и способность её пере-спросить — ОДНО значение: `auth.Principal` получил непубличное поле и метод `StillLive(ctx)`; `guard`его связывает; `pump` зовёт его ПЕРВЫМ ДЕЛОМ на каждой итерации и на отказ шлёт терминальный кадр `session_ended`с watermark соединения. Отдельный запрос стора `StillLive` БЕЗ клаузы idle. Окна нет — проверка на каждом тике | **ДВА раздельных живых сценария с сохранёнными транскриптами** (а) отзыв → поток кончился через 1 с, (б) короткий абсолютный потолок без отзыва → кончился ровно на потолке, пережив idle-окно · 9 юнит-пинов (`stream_session_test.go` 5, `sessions_test.go` 2, `principal_test.go` 2 — счёт командой `grep -c "func Test"`) · посадки — см. таблицу кампании ниже |
| **`PD-385`** (major) | Популяция «кончился, а деньги нет» вошла в `StalledRuns` (колонка `PHASE`), в гейдж `tm_platform_runs_stalled` и получила терминальную ручку `run abandon` (закрывает КАЖДЫЙ осиротевший холд, снимает отсрочку, штампует `settled_at`). Новые `AbandonVerdict`, `ErrMoneyAlreadyClosed`, `ErrSettlementNotStuck` | **Живое до/после на состоянии из штатных путей** (таблица выше) · 3 пина `internal/runs` + 3 пина CLI · посадки — см. таблицу кампании ниже |
| **`PD-384`** (minor) | Неудачей считается ВЕРДИКТ расчёта, а не только исчерпание бюджета: `settle` вернул три состояния, `settleOne` судит по ним. Первая неудача НЕ откладывается (сохраняет прежние 15 с пользователю), со второй — бэкофф, капнутый пятью минутами | Пины `TestTheFirstFailedSettlementIsRetriedAtOnce…`, `TestASettlementWithNoBaselineIsCountedRatherThanTreatedAsARace` · посадки — см. таблицу кампании ниже |
| **`PD-391`** (minor) | `AbandonRun` снимает `reconcile_after`В ОБЕИХ ветках | Пин `TestAbandoningAStalledRunGivesTheHoldBackOnTheNextSweepAndNotIn30Minutes` (растит 5 неудач штатными путями, потом сверяет холд ПОСЛЕ свипа на НЕДВИНУТЫХ часах) + `TestAbandoningAStuckSettlementClearsItsDeferralToo` · руководство `deploy/README.md` исправлено |
| **`PD-394`** (info) | Гард отрицательного расхода получил ИМЯ (`ErrNegativeSpend`) и пин | Посадка — см. таблицу кампании ниже. ⚠ Первая редакция пина мутацию НЕ ловила — ловил констрейнт схемы, ровно та транзитивность, о которой строка и говорит; пин переписан на `errors.Is` |
| **`PD-397`** (info) | Триггер ОТКЛОНЁН с разбором (сработал бы на каскадном удалении пользователя, которое миграция объявляет границей, и сломал бы законную фикстуру). Вместо него: проза сделана честной («держит КОД, а не схема») + гейт по SQL пакета, что ни один `update`/`delete` по `credit_ledger` не написан | Гейт `TestTheLedgerIsAppendOnlyInTheCodeThatWritesIt` (173 statements — число растёт с каждым новым SQL пакета; сверено по `FINAL2-verbose.log`) · посадка — см. таблицу кампании ниже |
| **`PD-376`** (minor, деньги) | Взят готовый пин пака `P8-REVIEW` — вариант `r1_` как более сильный (ходит настоящими дверями, наименьший в СЕРЕДИНЕ) — и усилен второй книгой, чтобы исполнялся и фильтр `r.book_id` | **Посадка `min`→`max` ПОЙМАНА**: чистая копия EXIT=0 → с мутацией EXIT=1, единственный красный тест — этот пин (`~/tm-p11/mut/m376_max/verdict.txt`). ⚠ Посадка ставилась на РАННЮЮ редакцию пина, без второй книги: то есть исполнение `min` как ВЫБОРА доказано, а исполнение фильтра `r.book_id` — нет, и это подписано пропуском в таблице кампании |
| **сверх пака** | `Settle` перестал выбрасывать флаг `applied` своей леджер-записи (`ErrSettlementKeySpent`) | Пин `TestASettlementWhoseKeyWasSpentIsRefusedRatherThanSilent` · посадка — см. таблицу кампании ниже. Достижимость сегодня НУЛЕВАЯ — второй холд на ту же попытку отказан; класс тот же, что у`PD-394` |
- **`PD-379`** (vuln, major): личность и способность её пере-спросить стали ОДНИМ значением —
`auth.Principal` получил `StillLive(ctx)`, `pump` зовёт его ПЕРВЫМ ДЕЛОМ на каждом тике и на отказ
| `r_listnarrow` | сузить settling-ветвь `StalledRuns` обратно к живым попыткам | `TestTheRunsListingShowsAStuckSettlementAndNamesThePhase`, `TestARunWhoseSettlementIsStuckReachesTheOperatorsSurfaces`, `TestASettlementInFlightIsNotInTheOperatorsTable`, `TestARunThatKeepsFailingBecomesTheOperatorsProblem` |
| `r_gaugenarrow` | то же в гейдже `tm_platform_runs_stalled` | `TestARunWhoseSettlementIsStuckReachesTheOperatorsSurfaces`, `TestARunThatKeepsFailingBecomesTheOperatorsProblem` |
| `r_oneorphan` | закрывать только ПЕРВЫЙ осиротевший холд | `TestAbandoningASettlementClosesEveryOrphanedHoldOfTheRun` |
| `r_defer391` | не снимать `reconcile_after` в живой ветке `AbandonRun` | `TestAbandoningAStalledRunGivesTheHoldBackOnTheNextSweepAndNotIn30Minutes` |
| `r_cheap384` | дешёвый провал расчёта снова не считается | пять тестов, включая `TestASettlementWithNoBaselineIsCountedRatherThanTreatedAsARace` и `TestAnOperatorCanEndAStuckSettlementAndTheMoneyComesBack` |
| `r_firstfast` | убрать быстрый первый ретрай расчёта | пять тестов, из них **три ДО-ПАКОВЫХ** (`TestADeferredSettlementIsNotChargedForTheNextRunOfTheSameBook`, `TestAFailedStatusCallLeavesTheMoneyExactlyWhereItWas`, `TestASettlementThatCannotBeReadLeavesTheHoldOpen`) |
(0.6.0). Всё исполнено, кроме ДВУХ пунктов с несогласием (аргументы ниже — обе позиции по норме
«несогласие говори»).
### Фиксы в дереве (сверх сданного пака; каждый с пином, посадки — в копии `~/tm-p9-mut2`)
### Фиксы в дереве (сверх сданного пака) — F1…F13
| # | Что | Пин | Посадка |
|---|---|---|---|
| F1 | **MAJOR(а) 1 МиБ**: `ingest.EncodeDecisions` рендерит с`SetEscapeHTML(false)` (документ читает движок, не браузер; выбор обоснован в комментарии) **+ жёсткий гейт**: рендер сверяется с зеркалом движкового капа `ingest.MaxDecisionsDocument` ДО спавна → `ErrBankDocumentTooLarge` → **413** (остаточная конвертная полоса ~40 байт закрыта гейтом, слово всегда канонное) | `TestTheRenderedDocumentDoesNotInflateEscapableBytes` (ingest) · `TestARenderedDocumentOverTheEngineCapIsRefusedBeforeTheSpawn` (runs, движок НЕ спавнится) · `TestARenderedDocumentOverTheEngineCapAnswers413` (httpapi) | M6 (возврат `json.Marshal`) и M7 (снятие гейта) пойманы топично |
| F2 | **MAJOR(б) мьютекс**: `lockBook(ctx)` — одноместный канал вместо `sync.Mutex`, ожидание наблюдает контекст; **бюджет двери ставится ДО очереди** (накрывает ожидание+вызов); Start/Resume передают свои ctx; отмена в очереди корректно декрементит refcount | `TestAWaiterWhoseContextEndsLeavesTheBookQueue` (+ leak-тест расширен) | M11 (ожидание игнорирует ctx) поймана |
| F3 | **Р1 окно стопа**: `ReadBookForRun` вырос полем `LiveRunAwaitingBank` (живая строка в `awaiting_bank`); предикат двери — `HasLiveRun && !LiveRunAwaitingBank`; Start по-прежнему держится на `HasLiveRun` (вторая строка сломала бы `runs_one_live_per_book`); худший случай окна — движок ещё дожёвывает → честный класс 12 → 503 «повтори». Довод в комментарии переписан, противоречие с`reconcile.go:1122` разрешено в пользу кода | `TestTheDoorOpensOnTheSigningStopWindow` (фикстура — как `control_test`: журнальный `awaiting_bank` без `finished_at`); обратная сторона держится старым `TestCorrectionsRefuseWhileTheBookIsBeingTranslated` | M8 (старый предикат) поймана — refuse-тест при этом остался зелёным |
| F4 | **Р2 миграция 00026**: два UPDATE сведены к ОДНОМУ — `draft_before := chapters_before` для ВСЕХ строк (та аппроксимация, которую ревью само проверило как самосогласованную для finished): пере-снятие БАЗ ПОСЛЕ работы прогона — единственный источник вечного недоезда −2·p_e — удалено; заодно умер и dispute про READ COMMITTED между двумя стейтментами (стейтмент один). Остаток по эпохам старого `chapters_before` честно назван в комментарии миграции. `migrations.sha256` пере-подписан (миграция не релизнута — слово оркестратора) | пин исполнением невозможен (тестовые БД наливаются миграциями ДО данных — старая семантика юнитом задним числом непроверяема); держится формулой + комментарием | — (названо, не скрыто) |
| F5 | **Р5 presence-vs-null**: все строковые члены `wireCorrection` — `nullableString` (парный `nullableInt`); явный `null` на любом = malformed ТИПА (рефьюз до правил присутствия); правила присутствия (`id`×tuple, `dst`/`kind` на decline) считают КЛЮЧИ | +3 кейса в `TestACorrectionRequestIsValidatedWholeWithPointers` (null-src при id · null-dst на decline · null-action) | M10 (снятие null-гейта) поймана |
| F6 | **Р6(i)**: комментарий SIGKILL-ветки переписан честно (ветка достижима только когда SIGTERM НЕ отработал; полу-приземлённая пара без отчёта — остаток в PD-407) | — (комментарий) | — |
| F7 | **Р6(ii)**: `bankStopGrace` 10 с → **30 с**, число обосновано замером движка в комментарии (11.3 с непрерываемого фолда на документе-максимуме, ×2.5 запас на медленный хост) | — (константа с доводом) | — |
| F8 | **Р6(iii)**: связка «ручка `TM_PLATFORM_RUN_BUDGET`× движковый кап 5000×11.3с» и цена понижения названы комментарием у бюджета двери (механизм не строился — слово оркестратора) | — | — |
| F9 | **Р7**: `Service.SweepCorrectionScratch()` — подметание `bank-corrections-*.json` на буте (вызов в композиционном корне `tmplatformd` ДО подъёма HTTP), файлы вне маски не трогаются | `TestBootSweepsOrphanedCorrectionDocuments`. ⚠ Честно: вызов ИЗ main юнитом не запинен — мутация «не звать на буте» ловится только чтением | M14 (маска мимо) поймана |
| F10 | **Д5**: комментарий `spawn.go`осбросе`--verify-bank` переписан с упразднённого D39.144-контракта на настоящее основание (память v16 покрывает карту; старое основание питало дырявый гард — названо в комментарии) | — | — |
| F11 | **Н5 hello**: при `resuming` hello несёт `last` клиента, не голову истории (свежий коннект — голову, как и `from` четырьмя строками ниже) | `TestAResumingHelloCarriesTheClientsOwnWatermark` (+ свежий коннект отдаёт голову) | M13 поймана |
| F12 | **Н1-гард**: `Resume` под мьютексом сверяется с новым `pgstore.LatestRunID` (тот же порядок, что `lastRun`) — не-последний прогон получает `ErrNotResumable`с человеческим доводом; реконсилерский RestartRun в гарде не нуждается (живой прогон всегда последний по `started_at`: пока он жив, новый не стартует) | `TestAnOlderRunCannotBeResumedOverANewerOne` (Now сдвигается между стартами — фикстурный Now дал бы одинаковый `started_at` и флейк по случайному id) | M12 поймана |
| F13 | **Канон 0.6.0** (третье сообщение): `ContractVersion` → 0.6.0; комментарий `wireProgress` переписан (сквозная доля и `stage` теперь КАНОННЫ, открытый словарь; оба условия исключения шапки — вывод платформой из тех же счётчиков и открытость — коду отвечают, проверила); протухший абзац про `stop_requested` заменён на «ни одного члена впереди канона»; комментарий у теста строки библиотеки сужен до правды (сам тест не тронут) | гейт версии (`reading_test`) снова зелёный | — |
Каждый несёт пин; какая посадка какой пин обязана валить — комментарии `Mutation caught:` в самих
тестах, поэтому колонки «Пин»/«Посадка» здесь не дублируются.
| # | Что |
|---|---|
| F1 | **MAJOR(а) 1 МиБ**: `ingest.EncodeDecisions` рендерит с`SetEscapeHTML(false)` (документ читает движок, не браузер; выбор обоснован в комментарии) **+ жёсткий гейт**: рендер сверяется с зеркалом движкового капа `ingest.MaxDecisionsDocument` ДО спавна → `ErrBankDocumentTooLarge` → **413** (остаточная конвертная полоса ~40 байт закрыта гейтом, слово всегда канонное) |
| F2 | **MAJOR(б) мьютекс**: `lockBook(ctx)` — одноместный канал вместо `sync.Mutex`, ожидание наблюдает контекст; **бюджет двери ставится ДО очереди** (накрывает ожидание+вызов); Start/Resume передают свои ctx; отмена в очереди корректно декрементит refcount |
| F3 | **Р1 окно стопа**: `ReadBookForRun` вырос полем `LiveRunAwaitingBank` (живая строка в `awaiting_bank`); предикат двери — `HasLiveRun && !LiveRunAwaitingBank`; Start по-прежнему держится на `HasLiveRun` (вторая строка сломала бы `runs_one_live_per_book`); худший случай окна — движок ещё дожёвывает → честный класс 12 → 503 «повтори». Довод в комментарии переписан, противоречие с`reconcile.go:1122` разрешено в пользу кода |
| F4 | **Р2 миграция 00026**: два UPDATE сведены к ОДНОМУ — `draft_before := chapters_before` для ВСЕХ строк (та аппроксимация, которую ревью само проверило как самосогласованную для finished): пере-снятие БАЗ ПОСЛЕ работы прогона — единственный источник вечного недоезда −2·p_e — удалено; заодно умер и dispute про READ COMMITTED между двумя стейтментами (стейтмент один). Остаток по эпохам старого `chapters_before` честно назван в комментарии миграции. `migrations.sha256` пере-подписан (миграция не релизнута — слово оркестратора) |
| F5 | **Р5 presence-vs-null**: все строковые члены `wireCorrection` — `nullableString` (парный `nullableInt`); явный `null` на любом = malformed ТИПА (рефьюз до правил присутствия); правила присутствия (`id`×tuple, `dst`/`kind` на decline) считают КЛЮЧИ |
| F6 | **Р6(i)**: комментарий SIGKILL-ветки переписан честно (ветка достижима только когда SIGTERM НЕ отработал; полу-приземлённая пара без отчёта — остаток в PD-407) |
| F7 | **Р6(ii)**: `bankStopGrace` 10 с → **30 с**, число обосновано замером движка в комментарии (11.3 с непрерываемого фолда на документе-максимуме, ×2.5 запас на медленный хост) |
| F8 | **Р6(iii)**: связка «ручка `TM_PLATFORM_RUN_BUDGET`× движковый кап 5000×11.3с» и цена понижения названы комментарием у бюджета двери (механизм не строился — слово оркестратора) |
| F9 | **Р7**: `Service.SweepCorrectionScratch()` — подметание `bank-corrections-*.json` на буте (вызов в композиционном корне `tmplatformd` ДО подъёма HTTP), файлы вне маски не трогаются |
| F10 | **Д5**: комментарий `spawn.go`осбросе`--verify-bank` переписан с упразднённого D39.144-контракта на настоящее основание (память v16 покрывает карту; старое основание питало дырявый гард — названо в комментарии) |
| F11 | **Н5 hello**: при `resuming` hello несёт `last` клиента, не голову истории (свежий коннект — голову, как и `from` четырьмя строками ниже) |
| F12 | **Н1-гард**: `Resume` под мьютексом сверяется с новым `pgstore.LatestRunID` (тот же порядок, что `lastRun`) — не-последний прогон получает `ErrNotResumable`с человеческим доводом; реконсилерский RestartRun в гарде не нуждается (живой прогон всегда последний по `started_at`: пока он жив, новый не стартует) |
| F13 | **Канон 0.6.0** (третье сообщение): `ContractVersion` → 0.6.0; комментарий `wireProgress` переписан (сквозная доля и `stage` теперь КАНОННЫ, открытый словарь; оба условия исключения шапки — вывод платформой из тех же счётчиков и открытость — коду отвечают, проверила); протухший абзац про `stop_requested` заменён на «ни одного члена впереди канона»; комментарий у теста строки библиотеки сужен до правды (сам тест не тронут) |
⚠ **Два фикса из тринадцати пина НЕ имеют, и это подписано, а не скрыто.****F4**: пин исполнением
НЕВОЗМОЖЕН — тестовые БД наливаются миграциями ДО данных, поэтому старая семантика задним числом
юнитом непроверяема; держится формулой и комментарием миграции. **F9**: вызов подметания ИЗ `main`
на буте не запинен — мутация «не звать на буте» ловится только чтением (сам подметатель запинен
`TestBootSweepsOrphanedCorrectionDocuments`).
### Два НЕСОГЛАСИЯ с диспозициями (норма «говори»)
вхождений; `golangci-lint run` → **0 issues**; `gofmt -l` пусто; `go vet` чисто (в составе линта).
⚠ Промежуточная батарея №2 имела РОВНО ОДИН честный FAIL — старый пин
`TestResumeIsRefusedWhenTheBookHasAnotherLiveRun` поймал, что первый вариант гарда F12 перекрывал
канонное слово `run_in_flight` при живом чужом прогоне; чинился КОД (словоразделение: живой сосед →
`run_in_flight`, финишировавший → `ErrNotResumable`), тест не тронут. Опись дерева:
`git status --short -- platform/` → **50 путей (39 M + 11 ??)**.
`go test ./... -race`с тремя гейтами → EXIT=0, 18 пакетов, 0 скипов, `FAIL|DATA RACE` — ноль;
линтер 0 issues.
⚠ **Промежуточная батарея №2 имела РОВНО ОДИН честный FAIL, и починен был КОД, а не тест:** старый
пин `TestResumeIsRefusedWhenTheBookHasAnotherLiveRun` поймал, что первый вариант гарда F12
перекрывал канонное слово `run_in_flight` при живом ЧУЖОМ прогоне. Развёл слова (живой сосед →
`run_in_flight`, финишировавший → `ErrNotResumable`); тест не тронут.
## ВОРКФЛОУ-РЕВЬЮ ДЕРЕВА P9 ОТРАБОТАНО — 16 линз, оба отложенных MAJOR подтверждены замером, сводка находок для оркестратора (сессия платформы, 28.08)
@ -1311,17 +1231,17 @@ bankCountsTx; проекции без утечек словаря/путей; bu
Дерево передаётся на лендинг. Опись: `git status --short -- platform/` → 31 изменённый + 9 новых
файлов, все в зоне; вне `platform/` не тронуто ничего.
### Таблица комплектности против §3 (пункт → сделано → каким ИСПОЛНЕНИЕМ подтверждено)
### Что построено по §3 (чем доказано — пины в дереве и живой пробой `docs/p9/door-live-probe.md`)
| §3 | Что сделано | Исполнение |
|---|---|---|
| §3.1 дверь | `POST /v0/books/{bookId}/bank/corrections` в `contractSurface` (монтаж по `Deps.Bank`, кап тела = канонный 1 МиБ = `DefaultMaxBody`); строгий декод (`DisallowUnknownFields` + запрет хвостовых байт), вся канонная валидация формы с JSON Pointer'ами; `Capabilities.bank_corrections_enabled` = факт монтажа; словарь шва `ingest/bankdecisions.go` (запрос v1 / отчёт v2, аллоулист); канал `runner.BankApply` (прямой чайлд, SIGTERM-грейс, потолок чтения); вердикт `runs.bankVerdict`; квитанция-проекция с переводом `edit_wave→refinement` и отказом на неизвестное слово; `refusals[]` в конверте `Problem` + коды `bank_corrections_refused`/`bank_corrections_incomplete` | живой пробой — `docs/p9/door-live-probe.md` (превью → правка → ретрай `already_applied` → отказ 409 с указателем → resume → 409 `run_in_flight` при живом прогоне, тела дословно); юнит-пины `internal/httpapi/bank_test.go` (7 на сдаче; 9 после приёмочных доборов — превью и обрыв на потолке), `internal/runs/bank_test.go` (6), `internal/ingest/bankdecisions_test.go` (2); посадки M1, M2 |
| §3.1 раскладка кодов | заказанное: 14→409 `bank_corrections_refused`+`refusals[]` · 15→503 `bank_corrections_incomplete` · 12→409 `run_in_flight` · тело>1МиБ→413 · >5000 и форма→400. Моя половина с доводом: **13→503 `service_unavailable`** (не мигрирован — оператор, транзиентно; различим от 15 по коду) · **19 и незнакомые члены полосы→503 `service_unavailable`** (рассинхрон сборок; какое из двух других ремеди — неизвестно по построению) · **10/11→500** (оба входа глагола рендерит платформа) · **exit 5 и таймаут бюджета→503 `service_unavailable`** (рестарт деплоя; SIGTERM-контракт глагола graceful). Три ремеди («пере-реши»/«повтори то же»/«позови оператора») не сливаются | пин-таблица `TestBankVerdictKeepsTheRemediesApart` + `TestTheDoorsFailuresKeepTheirRemediesApart`; посадка M1 (слияние 15 в 503-generic) поймана; опровергатель кодов: «(а) слияние ремедий — не опровергнуто по всем девяти строкам» |
| §3.2 синхронность | вызов синхронный, бюджет = `runBudget()` (60 с — класс вызовов движка); пер-книжный мьютекс `lockBook` в `runs.Service`, его берут corrections И `Start` И `Resume` (Start — та же гонка спавна, что resume); проверка живого прогона — ПОД мьютексом; гейт готовности книги (`readyToTranslate`) — как у Start (находка опровергателя) |`TestAResumeWaitsOutALiveCorrectionCall` (resume ЖДЁТ живой вызов двери, канал-гейтед фейк); `TestCorrectionsRefuseWhileTheBookIsBeingTranslated`; живьём — шаг 11 пробоя (409 `run_in_flight` на живом прогоне); синхронность ДЕРЖИТСЯ: живой вызов двери на стенде — доли секунды, потолок глагола 5000 подобран движком под таймаут вызывающего |
| §3.3 ключи | `TM_PLATFORM_ENGINE_KEYS_PATH` (абсолютный или отказ на буте; ⚠ суффикс `_PATH`, не `_FILE` — `*_FILE` в зоне значит «файл со значением секрета», гейт `TestEverySettingThisServiceReadsIsPrinted` это и поймал) → `runner.TranslateArgs` кладёт `--keys-file` ТОЛЬКО на `translate`; в окружение юнита ключи не кладутся; пусто = WARN на буте |`TestTheDeploymentKeyFileReachesTranslate`, `TestTheSpawnedUnitCarriesTheDeploymentKeyFile` (argv юнита + отсутствие ключей в Env); живьём: движок с несуществующим файлом падает громким «--keys-file … cannot be read», с файлом — пре-флайт пройден (лог пробоя); посадка M3 поймана |
| §3.4 полоса | ОДНА монотонная доля через обе волны: `done = draftBar + lastBar`, `total = draftWork + ceiling` (редактор) / `ceiling` (без), где `draftWork = clamp(chapters_before + ceiling − draft_before)` — знаменатель считает работу ЭТОГО прогона (правка по находке опровергателя: continuation поверх начернённого задела кончал ready на 50%); две базы в `StartRun`, пере-базирование при снятии стопа УДАЛЕНО; подпись `progress.stage` (`drafting`/`editing`, открытый словарь); кадр `progress` и старт-квитанция несут то же; миграция `00026` (live-прогоны — обе базы пере-сняты верными предикатами, законченные — аппроксимация, названо в самой миграции) | живьём: 0/6 drafting → стоп 3/6 editing → resume 3/6 (БЕЗ обнуления) → ready 6/6 (лог пробоя); пины `TestTheBarIsOneMonotonicFractionThroughTheSigningStop`, `TestARunOverADraftedBacklogOwesOnlyTheLastPass`, `TestADraftOnlyDeploymentCountsItsOneWaveOnce`, `TestASecondRunsBarStartsAtZeroOverAHalfFinishedBook`, `TestTheRunsBarNeverExceedsWhatItBought`; посадки M4, M5 пойманы |
| §3.5 PD-399 | `pending_decisions`/`complete` сняты со всех трёх носителей (`BankCounts`+кадр `EventBank`, `wireBankPage`, подзапрос к мёртвой `bank_decisions` ушёл); пин ПЕРЕПИСАН на отсутствие |`TestTheBankAggregatesRideOnTheFirstPageOnly` пинит ОТСУТСТВИЕ; живьём: `GET /bank` на стопе и после прогона — полей нет (лог пробоя, шаги 5 и 13); строка PD-399 → fixed |
| §3.6 конвенция пути | `runner.projectDB()` и парс `book.yaml` УДАЛЕНЫ; путь банк-экспорта берётся из конверта `artifacts.bank_export`, который движок публикует в `manifest --json` (движковая половина — d1eb8a9); `refreshBank` кормится манифестом той же refresh-пачки; движок без конверта = громкий отказ, долг ретраится |`TestTheBankIsReadAtThePathTheEnginePublished`, `TestAnEngineWithoutTheEnvelopeIsAFailureRatherThanAnEmptyBank`; живьём: банк пробной книги материализовался по опубликованному пути (шаг 13) |
| §3 | Что сделано |
|---|---|
| §3.1 дверь | `POST /v0/books/{bookId}/bank/corrections` в `contractSurface` (монтаж по `Deps.Bank`, кап тела = канонный 1 МиБ = `DefaultMaxBody`); строгий декод (`DisallowUnknownFields` + запрет хвостовых байт), вся канонная валидация формы с JSON Pointer'ами; `Capabilities.bank_corrections_enabled` = факт монтажа; словарь шва `ingest/bankdecisions.go` (запрос v1 / отчёт v2, аллоулист); канал `runner.BankApply` (прямой чайлд, SIGTERM-грейс, потолок чтения); вердикт `runs.bankVerdict`; квитанция-проекция с переводом `edit_wave→refinement` и отказом на неизвестное слово; `refusals[]` в конверте `Problem` + коды `bank_corrections_refused`/`bank_corrections_incomplete` |
| §3.1 раскладка кодов | заказанное: 14→409 `bank_corrections_refused`+`refusals[]` · 15→503 `bank_corrections_incomplete` · 12→409 `run_in_flight` · тело>1МиБ→413 · >5000 и форма→400. Моя половина с доводом: **13→503 `service_unavailable`** (не мигрирован — оператор, транзиентно; различим от 15 по коду) · **19 и незнакомые члены полосы→503 `service_unavailable`** (рассинхрон сборок; какое из двух других ремеди — неизвестно по построению) · **10/11→500** (оба входа глагола рендерит платформа) · **exit 5 и таймаут бюджета→503 `service_unavailable`** (рестарт деплоя; SIGTERM-контракт глагола graceful). Три ремеди («пере-реши»/«повтори то же»/«позови оператора») не сливаются |
| §3.2 синхронность | вызов синхронный, бюджет = `runBudget()` (60 с — класс вызовов движка); пер-книжный мьютекс `lockBook` в `runs.Service`, его берут corrections И `Start` И `Resume` (Start — та же гонка спавна, что resume); проверка живого прогона — ПОД мьютексом; гейт готовности книги (`readyToTranslate`) — как у Start (находка опровергателя) |
| §3.3 ключи | `TM_PLATFORM_ENGINE_KEYS_PATH` (абсолютный или отказ на буте; ⚠ суффикс `_PATH`, не `_FILE` — `*_FILE` в зоне значит «файл со значением секрета», гейт `TestEverySettingThisServiceReadsIsPrinted` это и поймал) → `runner.TranslateArgs` кладёт `--keys-file` ТОЛЬКО на `translate`; в окружение юнита ключи не кладутся; пусто = WARN на буте |
| §3.4 полоса | ОДНА монотонная доля через обе волны: `done = draftBar + lastBar`, `total = draftWork + ceiling` (редактор) / `ceiling` (без), где `draftWork = clamp(chapters_before + ceiling − draft_before)` — знаменатель считает работу ЭТОГО прогона (правка по находке опровергателя: continuation поверх начернённого задела кончал ready на 50%); две базы в `StartRun`, пере-базирование при снятии стопа УДАЛЕНО; подпись `progress.stage` (`drafting`/`editing`, открытый словарь); кадр `progress` и старт-квитанция несут то же; миграция `00026` (live-прогоны — обе базы пере-сняты верными предикатами, законченные — аппроксимация, названо в самой миграции) |
| §3.5 PD-399 | `pending_decisions`/`complete` сняты со всех трёх носителей (`BankCounts`+кадр `EventBank`, `wireBankPage`, подзапрос к мёртвой `bank_decisions` ушёл); пин ПЕРЕПИСАН на отсутствие |
| §3.6 конвенция пути | `runner.projectDB()` и парс `book.yaml` УДАЛЕНЫ; путь банк-экспорта берётся из конверта `artifacts.bank_export`, который движок публикует в `manifest --json` (движковая половина — d1eb8a9); `refreshBank` кормится манифестом той же refresh-пачки; движок без конверта = громкий отказ, долг ретраится |
### Числа сдачи (каждое — командой)
@ -1337,16 +1257,10 @@ bankCountsTx; проекции без утечек словаря/путей; bu
- Миграции: `00026` добавлена, отпечаток в `migrations.sha256`; `pgstore.Migrate` гонялся каждой
тестовой базой батареи (сотни накатов за прогон).
- **Посадки мутаций — 5, пойманы 5/5, в копии с каноном** (`cp -a --parents platform
**Пинг оркестратора №15 (09.08, D39.122): движковый пак «блокеры контракта» ПРИНЯТ и заленден `0e69bc1` — пять поверхностей для платформы существуют. ФИНАЛЬНЫЕ формы (менялись трижды за приёмку — старые в переписке игнорировать):**
- **Манифест глав:**`<project_db>.manifest.json`, `manifest_version: "tm-manifest-v2"` (v1 движок сам отклоняет); id главы = 16 hex (стабилен через пере-нарезку); **`unit.id = <chapterID>:<cutTag>:<firstChunkIdx>`** — тег разреза 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`; процент не отгружается — собирает клиент.
- **Банк:**`<project_db>.bank.json`(весь банк тремя статусами; id термов — длино-префиксированный хеш ключа уникальности, стабилен через пересборку) и `<project_db>.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 <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 показывает книжный) — скажите, заведём строку.
- **Банк:**`<project_db>.bank.json`— весь банк тремя статусами; id термов — длино-префиксированный хеш ключа уникальности, стабилен через пересборку. ⚠ **Второй сайдкар этого пункта, `<project_db>.bank-stop.json`, БОЛЬШЕ НЕ СУЩЕСТВУЕТ** (снесён D39.158). ⚠ И «ВСЕ сайдкары атомарно» было НЕВЕРНО (испр. оркестратором №18, 20.08, линза шва P7): атомарность каждого канала по отдельности — единственным носителем в `STACK_DECISIONS.md`, «Инвентарь каналов движка», и брать неатомарный на ЖИВОМ прогоне нельзя.
- **Потолок (строка 145): `--ceiling-usd` — КНИЖНЫЙ потолок в силе, не бюджет прогона** (D39.122), формула пересчёта амендирована D39.123 по `PD-158`. Разбор и три следствия — `STACK_DECISIONS.md` §21, здесь не дублируются. ⚠ **Живое из пинга — открытое предложение движка:** он готов провести `--ceiling-usd` и в `status`, чтобы мониторинг видел действующий потолок capped-прогона (сегодня `status` показывает книжный) — скажите, заведём строку.
**Вердикт: ПРИНЯТЬ С ФИКС-ЛИСТОМ.** Панель шести линз в изолированных копиях (слепая · контракт-конформность · деньги · шов · вне карты · ревью канона 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%» ни из чего не выводится — один носитель на факт, остальные указывают.
**Замер, обосновавший разворот 00016→00022, пере-выведен независимо** (свой бенчмарк на трёх вариантах в копии дерева): джойн **14.6 мс** · счётчик **5.3 мс** · без счётчика **2.9 мс**. Порядок и вывод подтверждаются, разворот законен. ⚠ Стоявшее здесь заключение «96% ни из чего не выводится» я СНЯЛ САМ — оно неверно, разбор в п.9 фикс-листа ниже: носители мерили РАЗНОЕ (холодный корпус против вакуумированного) и все честны.
**Собственные посадки мутаций ВНЕ вашего списка — 8, поймано 7:** долг не поставлен третьей концовкой ✅ · `edit_wave` не монотонен ✅ (⚠ сообщение пина печатает указатель вместо значения — `readmodel_test.go:918`) · снят `Vary: Accept-Encoding` ✅ · HEAD лишён валидатора ✅ · `reopen` снимает стоп банка кому угодно ✅ · погашение долга без сверки метки ✅ · вынос метки долга из закрывающей транзакции во второй оператор ✅ (техника `xmin` §36 работает, проверено исполнением).
**Собственные посадки мутаций ВНЕ вашего списка — 8, поймано 7** (состав — комментарии `Mutation caught:` в самих пинах; техника `xmin` §36 работает, проверено исполнением).
⚠ **НЕ поймано — дыра:** снятие `structure_version` И `revision` из `emitFrame`(`pgstore/events.go:72-73`) проходит ВСЮ батарею. Это два поля, которые канон требует на КАЖДОМ кадре (`EventBase`), и ваш же комментарий это утверждает. По вашей норме PD-1 свойство без пинящего теста считается НЕ закрытым.
⚠ **НЕ поймано — дыра, с тех пор ЗАКРЫТАЯ (п.2 фикс-листа):** снятие `structure_version` И `revision` из `emitFrame`проходило ВСЮ батарею — два поля, которые канон требует на КАЖДОМ кадре (`EventBase`). Пины стоят в `pgstore/events_test.go`с названной пойманной мутацией.
### Фикс-лист приёмки — завести строками СВОЕГО бэклога и регистра (зона моя не пишет)