From c04d3403bee5c909d5480e6bfcf865097768339a Mon Sep 17 00:00:00 2001 From: heaven Date: Fri, 28 Aug 2026 00:08:22 +0300 Subject: [PATCH] Stop carrying my own anchor debt: retarget nine by content, judge four whose token died with the code, and repair the anchor my own edit ran into --- CLAUDE.md | 2 +- docs/PROGRESS.md | 12 ++++++------ docs/architecture/17-seam-inbound-law.md | 4 ++-- docs/research/25-seam-cold-review.md | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 4971ab2d..9be97e14 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -11,7 +11,7 @@ Go-бэкенд издательского художественного пер 1. **Продукт:** издательское художественное качество перевода больших текстов — победить translationese; консистентные термины/голоса на всю книгу; чувствительный контент с учётом цензуры провайдеров. 2. **Мультиязычность:** движок ОДИН и общий; языко-/пара-/книго-специфика легитимна, но живёт в ДАННЫХ и отдельных модулях, встраиваемых архитектурно чисто (langpacks · пар-промпты · сид/бриф), и разрабатывается отдельно от ядра. Ревью-вопрос по умолчанию: «заработает ли пара, которой в репо ещё НЕТ, без правки Go?» -3. **Чистая архитектура:** бэкенд легко писать и поддерживать; механизм строится только там, где несёт качество/деньги — не ради галочки трекера. +3. **Чистая архитектура:** бэкенд легко писать и поддерживать; механизм строится только там, где несёт качество/деньги — не ради галочки трекера. **Техдолг НЕ держим (слово владельца 28.08):** надо выпилить — выпиливаем, надо подрефачить — рефачим; «оставим на потом» не диспозиция. ⚠ Оговорка владельца — ОСТОРОЖНО: рефактор идёт с пинами и на своём паке, а не попутно и не под лендинг чужого дерева. 4. **Современные подходы и харнесс-превосходство:** пайплайн и транспорт на уровне или впереди конкурентных харнесс-решений (калибровка — research/21–22); эмпирика решает — мерить, не верить. 5. **Экономика:** низкий COGS на дешёвых моделях с адресными дорогими вызовами; деньги видимы с первого вызова (телеметрия · потолки · явное согласие на пере-оплату). 6. **Детерминизм:** воспроизводимый прогон (снапшоты · банк памяти · golden) — повторение без сюрпризов и тихих перепокупок. diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 16956ed9..b8d1633a 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -141,25 +141,25 @@ | 196 | **Остаточный класс строки 176: неперехватываемые `fatal error` Go** (конкурентная запись в map, OOM, стек-оверфлоу) по-прежнему дают exit 2, то есть читаются платформой как «завершено с флагами» — `recover` их не ловит по устройству языка (подтверждено линзой шва исполнением). Строка 176 закрыта для ПАНИК, не для fatal. Лечение — вне exit-контракта: сторож процесса у платформы либо маркер живости в артефакте прогона | бэкенд/платформа | когда-нибудь | отдельное решение | приёмка №18, линза шва | | 197 | **Фикс-лист приёмки пака честности ФЧ-1…ФЧ-8** (D39.149; тело — [archive/PROGRESS-2026-08-17.md](archive/PROGRESS-2026-08-17.md), запись приёмки №18): три НЕПРИБИТЫЕ гарантии (снятие фильтра по книге в `CheckpointUsageForBook` не валит ни один тест — воспроизведено оркестратором · ключ ячейки без `chunk_idx` съедает деньги соседнего чанка · ветка «строка не пере-оценена» не ловится на продовом пути) · два докстринга `reprice.go` описывают НЕ то, что делает код · 187 исполнен наполовину (тот же снятый закон в докстринге теста и `backend/docs/D15.2-*.md:317`) · `HistoricalRows` не доезжает до `status`/`--json`, второй операторский текст (`--accept-rebill`) без basis · **ФЧ-5 РЕШЁН владельцем 17.08 (D39.150): прайсить по модели, которую стадия резолвит СЕЙЧАС, + округление вверх и оговорка в тексте согласия** · `Status()` жёстко падает при ошибке репрайсера против соседней политики деградации · паника поверх ceiling-стопа даёт `failed` вместо `paused` (PD-113) · три стейл-докстринга. Носителя-сессии НЕТ — по слову владельца 17.08 пока только записано | бэкенд | скоро (следующее касание денежного пути; ФЧ-5 обязателен до первого пере-прогона со сменой модели) | дофикс-промт ЛИБО попутно ближайшим паком | **D39.149**, **D39.150** | | 198 | **Апгрейд движка стирает замечания и счётчики книги БЕЗВОЗВРАТНО — композиция двух половин, каждая известна по отдельности** (приёмка P7, линза «вне карты», обе половины пере-прочитаны оркестратором). Платформа при смене `manifest_key` сносит ВСЕ `unit_resolutions` книги (`platform/internal/pgstore/readmodel.go:164-165`), рассчитывая, что поток их пере-наполнит; движок анонсирует юнит РОВНО ОДИН РАЗ за жизнь книги — ключ `unit::::` БЕЗ метки нарезки (`backend/internal/pipeline/events.go:399-400`), а леджер анонсов переживает прогоны (`backend/internal/store/outbox.go:96` «already announced… by one that ran before it») ⇒ совпавшие координаты не переанонсируются НИКОГДА. Обостряется порядком: долг на материализацию ставится только на ГРАНИЦАХ работы, поэтому первая зачистка после апгрейда случается в КОНЦЕ первого пост-апгрейдного прогона и сносит замечания ТОГО ЖЕ прогона, за который заплачено. Следствия на проводе: «0 из N» на переведённой книге навсегда · шкала снова предлагает купить переведённое · замечания читателя исчезают. **Лечится с обеих сторон:** движковая половина — метка нарезки в ключе анонса (решение зоны движка), платформенная — не сносить вслепую либо восстанавливать из экспорта. ⚠ Гейт холодного прогона: смысл упражнения — гонять книгу против МЕНЯЮЩЕГОСЯ движка, то есть первый же апгрейд обнулит библиотеку | бэкенд + платформа | **скоро (до первого реального пользователя И до холодного прогона с апгрейдами)** | связка: решение по ключу анонса в движке → правка платформы | приёмка P7 (D39.153) | -| 199 | **Канал доставки правок банка в движок НЕ построен — цепь «поправить/добавить термин → перегенерация» рвётся на шве платформа→движок** (расследование 20.08 по слову владельца; грепы пере-проверены оркестратором). ⚠ **ВЕРХНЕГО КРАЯ У ЦЕПИ БОЛЬШЕ НЕТ (испр. 22.08 при лендинге P8-FIX, D39.154).** Прежняя редакция строки опиралась на ручку `submitBankDecisions` с полем `dst`, куда пользователь мог впечатать исправленный перевод, — **она СНЯТА вместе со всей пер-термной моделью подписи** (`PD-370`, ратификация D39.144). То есть сегодня разорван не один конец, а оба: снизу движок умеет точечную ре-редактуру по вхождениям за $0 на незатронутых юнитах со сметой и порогом согласия (`stagerun.go:88-107`, `repin.go`/`rebill.go`), а сверху ручки «поправить/добавить термин» нет НИ В ЗОНЕ, НИ В КАНОНЕ — на 17 путей канона запись в банк была ровно одна, и она ушла. Это ужесточает строку, а не смягчает: раньше не хватало трубы посередине, теперь не хватает ещё и входа. ⚠ **И третья половина, вскрытая сквозной трассировкой 22.08 — «снизу движок готов» верно ЛИШЬ НАПОЛОВИНУ, сверено кодом:** (а) **правка существующего термина** доедет до РЕДАКТОРА, но не до ЧЕРНОВИКА — `loadMinedDelta` принудительно ставит `Source:"mined"` (`backend/internal/pipeline/mining.go`), а базовый банк черновой волны mined-строки ИСКЛЮЧАЕТ (`seeding.go:143-153`, `if row.Source == "mined" { continue }`). Чтобы правка формировала сам черновик, она должна ехать через `glossary_seed`, а это сдвиг БАЗОВОГО снапшота ⇒ пере-оплата всей черновой волны. То есть «дёшево» работает только для редакторского слоя; (б) **вход в пере-генерацию после прочтения закрыт двумя флагами, которых платформа не передаёт**: `--resnapshot` и `--accept-rebill` сверх порога. `platform/internal/runner/engine.go` `TranslateArgs` собирает РОВНО `translate --config <...> [--verify-bank] [ceiling]` и больше ничего; (в) команды, превращающей карту подписи в подписанный `mined_delta`, у движка НЕТ — в `tmctl` девять подкоманд, среди них ни `sign`, ни `promote`, а банк-экспорт объявлен «проекция, никогда не источник». Писателя файла надо НАЗВАТЬ, и это ровно развилка (а) выше. ⚠⚠ **И ТРИ МИНЫ В САМОМ ФОРМАТЕ ФАЙЛА, которые обязана знать сессия, которая его напишет (сверено кодом 22.08):** **(1) копи-паст карты подписи НЕ РАБОТАЕТ.** Карта пишется вся со `Status: "auto"` (`internal/miner/miner_emit.go`, док прямо: «Every term stays status:auto — that is the load-bearing line», и это запинено). А `unsignedEngineSurfaces` выбрасывает `Source=="mined" && Status!="approved"` (`mining.go:710-719`) ⇒ механически скопированные строки отфильтруются как неподписанные и стоп НЕ снимется — бесконечная петля. Писать надо ЯВНЫЙ `status: approved`. ⚠ Тонкость в другую сторону: ОПУЩЕННЫЙ статус в сид-загрузчике дефолтится в `approved` (`membank/memseed.go:96`=`status = "approved"`), то есть файл БЕЗ поля статуса работает, а файл со скопированным `auto` — нет. **(2) терм БЕЗ `dst` промотить нельзя вообще:** approved/draft с пустым `dst` — ПАДЕНИЕ загрузки сида, то есть срыв всего прогона (`memseed.go:83-86`). А `dst` есть не у всех — часть термов WHICH-only (`mining.go` считает `withDst` именно поэтому). Для них единственный очищающий глагол — ОТКАЗ (`mined_rejects`, берёт только `src`). **(3) хвост реверс-секции ломает «один ОК» на больших книгах:** `reverseSectionTerms` режет кап 200 ПОСЛЕ фильтров, и движок сам предупреждает — «the tail is NOT in this signature map and re-proposes on the next run». При >200 banknote-only поверхностей подпись всей карты стоп НЕ снимет: приедет свежая двухсотка, и актов подписи понадобится ceil(N/200). Носителя между ними нет: колонки `action`/`dst` не читает НИ ОДИН SELECT (единственная выборка `readmodel.go:336` проверяет лишь факт наличия строки) · `mined_delta ⚠ **СУЖЕНА 27.08 приёмкой (D39.159, свип по норме §3 п.8): движковая половина ЦЕПИ ПОСТРОЕНА.** `tmctl bank-apply` заленджен `d1eb8a9` и ратифицирован D39.158 — писатель файлов решений существует, правку термина есть куда положить. Открытым остаётся ровно ПЛАТФОРМЕННЫЙ конец: воркер, который дёргает глагол перед возобновлением (пункт (2в) очереди). Формулировка «канал НЕ построен» с этого дня неверна и отправляла бы следующий пак строить построенное |mined_rejects` по Go платформы = **0** вхождений · воркер, который должен переносить решения в эти файлы перед resume, описан в комментарии её же миграции (`00002_readmodel.sql:172-174`) и НЕ построен. **Делится надвое:** (а) **труба** — платформа пишет решения в два файла, которые движок читает. ⚠ **«Контракт файлов существует, проектировать нечего» — ОПРОВЕРГНУТО КОДОМ 21.08** (опровергатель промта, пере-проверено оркестратором построчно): движок читает `mined_delta`/`mined_rejects`, ТОЛЬКО если их путь объявлен в `book.yaml` (`backend/internal/pipeline/mining.go:822`=`if r.Book.MinedDelta == ""`), а объявленный ключ с НЕсуществующим файлом валит загрузку конфига целиком (`backend/internal/config/book.go:254`=`is not readable`). Ключей нет ни в шаблоне оператора, ни в дев-стенде (`backend/example/book.yaml` — ноль вхождений), и добавить их платформа не может молча: она пишет `book.yaml` ОДИН раз под `O_EXCL` и «никогда не перезаписывает» (`platform/internal/books/render.go:106-109`=`writes the file once and never again`), а схему движка знать не должна — это ратифицированная форма Б (D39.110 §2б). **Значит развилка есть и она упирается в ратифицированную границу владения `book.yaml`:** объявляет ли ключи платформа при рождении книги (и создаёт ли два пустых файла сразу, иначе движок не стартует) · несёт ли их шаблон оператора · двигается ли граница владения. Решать её — ратификацией, не паком. Закрывает и честность `decline` (PD-277 — его текст сегодня относит работу к отложенной 192, пере-диспозиция нужна тем же решением); (б) **отложенное владельцем** — правки ПОСЛЕ прочтения уже переведённой книги: там правка банка двигает снапшот, движок падает громко без явного согласия на пере-оплату, а `resnapshot|accept-rebill` по Go платформы = 0 ⇒ прогон не перегенерирует, а УПАДЁТ (сегодня не кусает только потому, что до движка не доезжает ничего). Половина (б) — территория строки **192**. ⚠ **ПОПРАВКИ 23.08 (№19, консилиум шва):** (1) формулировка «ни на одном конце» СНЯТА — принимающий конец ДВИЖКА построен (`pipeline/mining.go:784`=`if r.Book.MinedRejects == ""`), не построен ПЛАТФОРМЕННЫЙ путь целиком; (2) тело развилки с четырьмя вариантами и ценой лежит ТОЛЬКО в `platform/docs/archive/platform-PROGRESS-P8.md:176-205`=`Двигается ратифицированная граница владения`; (3) есть СЕДЬМОЙ вариант, которым развилка не решается, а РАСТВОРЯЕТСЯ: движок дефолтит `mined_delta`/`mined_rejects` по конвенции каталога (как уже делает с `project_db` — `config/book.go:189`=`b.ProjectDB = filepath.Join(dir, b.BookID+".db")` — и как уже читает `.auto-bank.yaml`, `mining.go:644`), писателем становится движковый $0-глагол ⇒ ключей никто не объявляет, файлов заранее никто не создаёт; (4) зона дополнена ВЛАДЕЛЬЦЕМ: канон зовёт половину (а) блокером на нём; (5) ⚠ **ГРАНИЦА, которую труба НЕ снимает и которая до 23.08 жила только внутри чужого списка ошибок:** правка, доставленная через `mined_delta`, доезжает до РЕДАКТОРСКОЙ волны, но НЕ формирует черновик — черновая волна выбирает по банку БЕЗ mined-строк (`backend/internal/pipeline/seeding.go:140`=`The DRAFT wave selects over a BASE-scoped bank`). Для читателя это не дефект: отгружается выход редактора, а юниты с термином пере-редактируются, незатронутые пере-пиниваются за $0 (`backend/internal/pipeline/stagerun.go:88-107`=`POINTWISE RE-EDIT`). Настоящая цена тоньше и НЕ ИЗМЕРЕНА: редактор латает черновик, построенный вокруг старого термина (согласование, род, игра слов). Правка, формирующая сам черновик, — это `glossary_seed`, то есть сдвиг базового снапшота с пере-оплатой; территория строки 192, глубину решает владелец ПОСЛЕ фазы Д | владелец + платформа + бэкенд | (а) РАСТВОРЕНА D39.156 · (б) гейчена 192 | (а) движковый пак (дефолты путей + глагол приёма правок) и платформенный (воркер решений); ⚠ прежняя запись звала «дофикс-пак платформы» — такого носителя в репозитории нет ни одного, имя мёртвое, испр. 23.08 · (б) с циклом чтения | расследование 20.08, D39.153 | +| 199 | **Канал доставки правок банка в движок НЕ построен — цепь «поправить/добавить термин → перегенерация» рвётся на шве платформа→движок** (расследование 20.08 по слову владельца; грепы пере-проверены оркестратором). ⚠ **ВЕРХНЕГО КРАЯ У ЦЕПИ БОЛЬШЕ НЕТ (испр. 22.08 при лендинге P8-FIX, D39.154).** Прежняя редакция строки опиралась на ручку `submitBankDecisions` с полем `dst`, куда пользователь мог впечатать исправленный перевод, — **она СНЯТА вместе со всей пер-термной моделью подписи** (`PD-370`, ратификация D39.144). То есть сегодня разорван не один конец, а оба: снизу движок умеет точечную ре-редактуру по вхождениям за $0 на незатронутых юнитах со сметой и порогом согласия (`stagerun.go:88-107`, `repin.go`/`rebill.go`), а сверху ручки «поправить/добавить термин» нет НИ В ЗОНЕ, НИ В КАНОНЕ — на 17 путей канона запись в банк была ровно одна, и она ушла. Это ужесточает строку, а не смягчает: раньше не хватало трубы посередине, теперь не хватает ещё и входа. ⚠ **И третья половина, вскрытая сквозной трассировкой 22.08 — «снизу движок готов» верно ЛИШЬ НАПОЛОВИНУ, сверено кодом:** (а) **правка существующего термина** доедет до РЕДАКТОРА, но не до ЧЕРНОВИКА — `loadMinedDelta` принудительно ставит `Source:"mined"` (`backend/internal/pipeline/mining.go`), а базовый банк черновой волны mined-строки ИСКЛЮЧАЕТ (`seeding.go:143-153`, `if row.Source == "mined" { continue }`). Чтобы правка формировала сам черновик, она должна ехать через `glossary_seed`, а это сдвиг БАЗОВОГО снапшота ⇒ пере-оплата всей черновой волны. То есть «дёшево» работает только для редакторского слоя; (б) **вход в пере-генерацию после прочтения закрыт двумя флагами, которых платформа не передаёт**: `--resnapshot` и `--accept-rebill` сверх порога. `platform/internal/runner/engine.go` `TranslateArgs` собирает РОВНО `translate --config <...> [--verify-bank] [ceiling]` и больше ничего; (в) команды, превращающей карту подписи в подписанный `mined_delta`, у движка НЕТ — в `tmctl` девять подкоманд, среди них ни `sign`, ни `promote`, а банк-экспорт объявлен «проекция, никогда не источник». Писателя файла надо НАЗВАТЬ, и это ровно развилка (а) выше. ⚠⚠ **И ТРИ МИНЫ В САМОМ ФОРМАТЕ ФАЙЛА, которые обязана знать сессия, которая его напишет (сверено кодом 22.08):** **(1) копи-паст карты подписи НЕ РАБОТАЕТ.** Карта пишется вся со `Status: "auto"` (`internal/miner/miner_emit.go`, док прямо: «Every term stays status:auto — that is the load-bearing line», и это запинено). А `unsignedEngineSurfaces` выбрасывает `Source=="mined" && Status!="approved"` (`mining.go:710-719`) ⇒ механически скопированные строки отфильтруются как неподписанные и стоп НЕ снимется — бесконечная петля. Писать надо ЯВНЫЙ `status: approved`. ⚠ Тонкость в другую сторону: ОПУЩЕННЫЙ статус в сид-загрузчике дефолтится в `approved` (`membank/memseed.go:96`=`status = "approved"`), то есть файл БЕЗ поля статуса работает, а файл со скопированным `auto` — нет. **(2) терм БЕЗ `dst` промотить нельзя вообще:** approved/draft с пустым `dst` — ПАДЕНИЕ загрузки сида, то есть срыв всего прогона (`memseed.go:83-86`). А `dst` есть не у всех — часть термов WHICH-only (`mining.go` считает `withDst` именно поэтому). Для них единственный очищающий глагол — ОТКАЗ (`mined_rejects`, берёт только `src`). **(3) хвост реверс-секции ломает «один ОК» на больших книгах:** `reverseSectionTerms` режет кап 200 ПОСЛЕ фильтров, и движок сам предупреждает — «the tail is NOT in this signature map and re-proposes on the next run». При >200 banknote-only поверхностей подпись всей карты стоп НЕ снимет: приедет свежая двухсотка, и актов подписи понадобится ceil(N/200). Носителя между ними нет: колонки `action`/`dst` не читает НИ ОДИН SELECT (единственная выборка `readmodel.go:336` проверяет лишь факт наличия строки) · `mined_delta ⚠ **СУЖЕНА 27.08 приёмкой (D39.159, свип по норме §3 п.8): движковая половина ЦЕПИ ПОСТРОЕНА.** `tmctl bank-apply` заленджен `d1eb8a9` и ратифицирован D39.158 — писатель файлов решений существует, правку термина есть куда положить. Открытым остаётся ровно ПЛАТФОРМЕННЫЙ конец: воркер, который дёргает глагол перед возобновлением (пункт (2в) очереди). Формулировка «канал НЕ построен» с этого дня неверна и отправляла бы следующий пак строить построенное |mined_rejects` по Go платформы = **0** вхождений · воркер, который должен переносить решения в эти файлы перед resume, описан в комментарии её же миграции (`00002_readmodel.sql:172-174`) и НЕ построен. **Делится надвое:** (а) **труба** — платформа пишет решения в два файла, которые движок читает. ⚠ **«Контракт файлов существует, проектировать нечего» — ОПРОВЕРГНУТО КОДОМ 21.08** (опровергатель промта, пере-проверено оркестратором построчно): движок читает `mined_delta`/`mined_rejects`, ⚠ **прежняя формулировка «ТОЛЬКО если их путь объявлен в `book.yaml`» УСТАРЕЛА и снята 28.08:** гард `if r.Book.MinedDelta == ""` снесён лендингом `d1eb8a9` — пути стали КОНВЕНЦИОННЫМИ (D39.158), и объявлять их в `book.yaml` больше не требуется (`backend/internal/pipeline/mining.go:822`), а объявленный ключ с НЕсуществующим файлом валит загрузку конфига целиком (`backend/internal/config/book.go:254`=`is not readable`). Ключей нет ни в шаблоне оператора, ни в дев-стенде (`backend/example/book.yaml` — ноль вхождений), и добавить их платформа не может молча: она пишет `book.yaml` ОДИН раз под `O_EXCL` и «никогда не перезаписывает» (`platform/internal/books/render.go:106-109`=`writes the file once and never again`), а схему движка знать не должна — это ратифицированная форма Б (D39.110 §2б). **Значит развилка есть и она упирается в ратифицированную границу владения `book.yaml`:** объявляет ли ключи платформа при рождении книги (и создаёт ли два пустых файла сразу, иначе движок не стартует) · несёт ли их шаблон оператора · двигается ли граница владения. Решать её — ратификацией, не паком. Закрывает и честность `decline` (PD-277 — его текст сегодня относит работу к отложенной 192, пере-диспозиция нужна тем же решением); (б) **отложенное владельцем** — правки ПОСЛЕ прочтения уже переведённой книги: там правка банка двигает снапшот, движок падает громко без явного согласия на пере-оплату, а `resnapshot|accept-rebill` по Go платформы = 0 ⇒ прогон не перегенерирует, а УПАДЁТ (сегодня не кусает только потому, что до движка не доезжает ничего). Половина (б) — территория строки **192**. ⚠ **ПОПРАВКИ 23.08 (№19, консилиум шва):** (1) формулировка «ни на одном конце» СНЯТА — принимающий конец ДВИЖКА построен (там же, `pipeline/mining.go:784`; гард снят тем же лендингом), не построен ПЛАТФОРМЕННЫЙ путь целиком; (2) тело развилки с четырьмя вариантами и ценой лежит ТОЛЬКО в `platform/docs/archive/platform-PROGRESS-P8.md:176-205`=`Двигается ратифицированная граница владения`; (3) есть СЕДЬМОЙ вариант, которым развилка не решается, а РАСТВОРЯЕТСЯ: движок дефолтит `mined_delta`/`mined_rejects` по конвенции каталога (как уже делает с `project_db` — `config/book.go:189`=`b.ProjectDB = filepath.Join(dir, b.BookID+".db")` — и как уже читает `.auto-bank.yaml`, `mining.go:644`), писателем становится движковый $0-глагол ⇒ ключей никто не объявляет, файлов заранее никто не создаёт; (4) зона дополнена ВЛАДЕЛЬЦЕМ: канон зовёт половину (а) блокером на нём; (5) ⚠ **ГРАНИЦА, которую труба НЕ снимает и которая до 23.08 жила только внутри чужого списка ошибок:** правка, доставленная через `mined_delta`, доезжает до РЕДАКТОРСКОЙ волны, но НЕ формирует черновик — черновая волна выбирает по банку БЕЗ mined-строк (`backend/internal/pipeline/seeding.go:140`=`The DRAFT wave selects over a BASE-scoped bank`). Для читателя это не дефект: отгружается выход редактора, а юниты с термином пере-редактируются, незатронутые пере-пиниваются за $0 (`backend/internal/pipeline/stagerun.go:88-107`=`POINTWISE RE-EDIT`). Настоящая цена тоньше и НЕ ИЗМЕРЕНА: редактор латает черновик, построенный вокруг старого термина (согласование, род, игра слов). Правка, формирующая сам черновик, — это `glossary_seed`, то есть сдвиг базового снапшота с пере-оплатой; территория строки 192, глубину решает владелец ПОСЛЕ фазы Д | владелец + платформа + бэкенд | (а) РАСТВОРЕНА D39.156 · (б) гейчена 192 | (а) движковый пак (дефолты путей + глагол приёма правок) и платформенный (воркер решений); ⚠ прежняя запись звала «дофикс-пак платформы» — такого носителя в репозитории нет ни одного, имя мёртвое, испр. 23.08 · (б) с циклом чтения | расследование 20.08, D39.153 | | 200 | **Сквозная полоса прогресса вместо пофазной — требование владельца 20.08. ⚠ ДИСПОЗИЦИЯ 27.08 (D39.160, слово владельца): едет с ПЛАТФОРМЕННЫМ паком (2в), а НЕ с контрактным минором.** Причина механическая: гейт версии требует совпадения канона и деплоя в момент лендинга, а сервер прогресс по-новому не считает и не будет до (2в) — канон, объявляющий новую семантику при старом сервере, лжёт ровно так, как гейт запрещает. Требование не ослаблено, перенесён только носитель. Сегодня прогресс считается ПО ОТРЕЗКАМ и при снятии стопа начинается с нуля (канон §Progress: «When a stop is cleared the counter starts again from zero») ⇒ пользователь видит 100% после черновика и обнуление — владелец назвал это дефектом подписанного, не вкусовщиной. Форма решения: ОДНА доля на всю работу прогона, считает СЕРВЕР (иначе клиент снова начнёт знать про фазы), плюс подпись «что делается сейчас» и неопределённый индикатор там, где доли в главах нет. ⚠ Веса «условно 10/40/40» НЕ нужны для переводческой части: движок объявляет знаменатель каждого прохода и ноль для прохода, которого на деплое нет (`backend/internal/pipeline/events.go:297-323`) ⇒ доля = сделано по всем проходам ÷ запланировано, без подобранных констант, и на деплое без редактора честно доходит до 100%. Догадка остаётся только у банка: поток прогресса несёт лишь два переводческих прохода, майнинг знаменателя не объявляет — **вопрос движку: может ли майнинг объявлять знаменатель** (он идёт по главам). Цена: правит канон §Progress + `Book.chapters_done`, то есть новый минор; пока фронт заморожен, минор бесплатен ⚠ **СВЯЗКА, поднимающая приоритет (найдена контрактной сессией 27.08, пере-проверена приёмкой):** после D39.161 главный пользовательский цикл — «поправил термины → запустил прогон, чтобы движок их применил» — это РОВНО сценарий открытой строки `PD-281`: прогон-применение над полной книгой стоит `0/N` и не достигает единицы. **Дверь правок сделала невидимый прогон флагманским, а не редким.** И канон 0.5.0 в §Progress ПО-ПРЕЖНЕМУ обещает «the fraction always reaches one» (`openapi.yaml:1343`) — то есть после сегодняшнего минора канон обещает то, что деплой на этом цикле нарушает. Прогресс-минор обязан либо дать числителю серверную семантику пере-разрешения, либо оговорить обещание; оговорку в 0.5.0 не вносили, потому что D39.160 прямо запретил трогать прогресс в том миноре. ⚠ `PD-281` — ПЛАТФОРМЕННАЯ строка, в бэкенд-пак не входит по зоне | контракт → платформа + бэкенд | скоро (следующий минор контракта) | контрактная сессия + платформенная половина; вопрос движку о знаменателе майнинга | слово владельца 20.08, D39.153 | | 201 | **Движковое «Глава N» доезжает читателю ВНУТРИ текста, обходя дисциплину `heading: null`** (линза шва P7, пере-прочитано оркестратором): `backend/internal/pipeline/export.go:230` приклеивает детерминированный порядковый к экспортному тексту первой юнит-главы (`ce.FinalText = chunk.ApplyHeading(...)`), колонка `Source` при этом остаётся heading-stripped. Платформа переносит обе как есть и честно отдаёт `heading: null` — то есть клиент нарисует СВОЙ порядковый на языке своего интерфейса над абзацем, который уже начинается с русского «Глава N», а исходная колонка соответствующего маркера не несёт: пара визуально рассинхронизирована на каждой первой главе. Конкретное следствие открытого К-2 контракта; родня движковой строки **160** (глава без заголовка) | бэкенд | когда-нибудь (с 160) | отдельное решение | приёмка P7 (D39.153) | | 202 | **Живой перевод книги НАСКВОЗЬ через API платформы не гонялся НИ РАЗУ** (obstacle сессии P7, подтверждён приёмкой): всё после «прогон стартовал и остановился» — подпись банка, halt на потолке, резюм после пополнения — покрыто батареей против живого Postgres, но с движком-заглушкой; настоящий манифест, сайдкар банка и причины флагов будут ПЕРВЫМИ данными, форму которых платформа не сочинила сама. Форма проверки (решена): только через API, как ходил бы клиент — интейк → старт → живой движок с ключами провайдера → поток событий → подпись и продолжение → чтение глав/юнитов/замечаний/банка и сверка с тем, что движок реально произвёл; качество перевода НЕ предмет (полигон). Цена ≈ $0.05 на десятиглавую книгу. **Решение владельца 20.08: ПОСЛЕ холодного прогона движка** — гонять одну неготовую систему против другой смысла нет | платформа | скоро (гейт: холодный прогон движка) | короткая сессия по промту оркестратора | слово владельца 20.08, D39.153 | -| 203 | **Хвосты контракта после синка 0.4.0 — ОТКРЫТА ЧЕТВЕРТЬ, остальное исполнено** (тела релеев — `platform/docs/archive/P7_ACCEPTANCE_HANDOFF_2026-08-17.md` §7; сюда переписаны НЕ тела, а статус — один носитель на факт). ⚠ Сверено грепом при лендинге, потому что первая редакция этой строки уже разошлась с деревом: **(б) ИСПОЛНЕН** — срезанная фраза компаньона §2.14 стоит на месте (`14-api-contract/README.md:536`=`различать причины отказа клиент не может по замыслу`) · **(д) ИСПОЛНЕН** — клетка приложения А заполнена `unspecified` (`README.md:1376` — ⚠ первая редакция писала `:1391`, это строка ДРУГОГО приложения; испр. 20.08) · **(г) наполовину** — `unspecified` ратифицирован в каноне (`openapi.yaml:1784-1787`), открыта только ГРАНИЦА ступеней замечаний (две ступени при девяти рангах движка) → это строка **148** · **(и)** закрыт полем `stop_requested` (D39.152) · (л)(м)(н)(о) закрыты контрактной сессией. **ОТКРЫТО РЕАЛЬНО:** ⚠ **(а) ИСПОЛНЕН 27.08 минором 0.5.0 (D39.161)** — канон получил предупреждение о конверте вне `/v0`; открытым по строке остаётся (к). Прежний текст пункта: (а) канон-половина про `/auth` — компаньон поверхность описывает (§2.14), а канон говорит только «Signing in is not part of this surface» (`openapi.yaml:35-36`) и НЕ предупреждает, что конверт вне `/v0` может прийти БЕЗ обязательного `code`; сгенерированный по канону клиент об это спотыкается · (з) ⚠ **СНЯТО КАК РАТИФИКАЦИЯ 22.08 — конфликта моделей НЕТ, я нёс причину и следствие наоборот.** Прежняя редакция писала: «D39.144 ратифицировал подпись = ОДИН акт, движок держит пер-термный гейт полноты, одно из двух должно уступить». Сквозная трассировка это опровергла КОДОМ: гейт проверяет полноту не ПОДПИСЕЙ, а ФАЙЛА решений, и ОДИН акт его снимает — `loadMinedDelta` штампует только `Source`, статус не трогает (`mining.go:855-867`) → опущенный статус дефолтится в `approved` (`membank/memseed.go:96`=`status = "approved"`) → `unsignedEngineSurfaces` выбрасывает лишь `Source=="mined" && Status!="approved"` (`mining.go:710-719`) ⇒ промоутнутые строки остаются сид-поверхностями, дельта пустеет, стоп снимается. Шапка файла говорит это прямым текстом. **Один файл со всем банком = один ОК.** Обход в платформе (`--verify-bank` не передаётся на resume) — костыль под ОТСУТСТВУЮЩУЮ ДОСТАВКУ, а не под расхождение моделей: как только доставка появится, флаг можно просто оставить включённым. Настоящий и единственный блокер темы — владение `book.yaml`, строка **199(а)**. Ратификации здесь НЕ нужно; остаётся снять обход одной боевой строкой (`platform/internal/runs/spawn.go`) и семью строками ставшего ложным обоснования — работа платформенного пака, не решение владельца · (к) ключ `project_db` в `book.yaml`: договорить, кто им владеет — шаблон оператора его не содержит, движок делает необязательным. ⚠ **Следствие «на штатном деплое банк не читается» ОПРОВЕРГНУТО живым кодом** (проверено при лендинге): акт 5 внёс фолбэк `.db` и назвал прежний отказ багом (`platform/internal/runner/artifacts.go:82-90`=`The key is OPTIONAL to the engine`); первая редакция этой строки повторила клейм хендоффа зоны, не сверив с деревом — остаток чисто договорной · **(л) остаток закрытой строки 148 — Приложение А компаньона.** ⚠ Сама 148 СНЯТА 21.08 по слову владельца: она держала мир, которого больше нет — «карта потеряна инцидентом 08.08, реконструкция у оркестратора №15, решить, брать ли черновиком». Реконструкция брать нечего: прежняя карта вела «причина движка → ФРАЗА», то есть предполагала, что фразу рисует СЕРВЕР, а батч 0.3.0 эту политику ОТМЕНИЛ — `Note` несёт машинный `code`, фразу рисует клиент, `Note.message` с провода снят (`14-api-contract/README.md:1097`=`Карта «причина → КОД контракта`). Реально пуста другая клетка: соответствие причин движка кодам замечаний, и правила её заполнения в компаньоне уже написаны (фраза по ДОККОММЕНТУ `disposition.go`, а не по имени константы — инверсия уже стоила двух фраз; класс 2 схлопывается в ОДИН код). Исполнитель — контрактная сессия, не оркестратор ⚠ **ПЕРЕ-ДИСПОЗИЦИЯ 27.08 (D39.158 п.7):** хвост (з) закрыт — «ратификации не нужно» отвечало на вопрос про один файл решений, не про флажок; движковый гейт полноты снят, не обойдён.| контракт/доки | скоро (следующее касание контракта, вместе с 200) | контрактная сессия | приёмка P7 (D39.153) | +| 203 | **Хвосты контракта после синка 0.4.0 — ОТКРЫТА ЧЕТВЕРТЬ, остальное исполнено** (тела релеев — `platform/docs/archive/P7_ACCEPTANCE_HANDOFF_2026-08-17.md` §7; сюда переписаны НЕ тела, а статус — один носитель на факт). ⚠ Сверено грепом при лендинге, потому что первая редакция этой строки уже разошлась с деревом: **(б) ИСПОЛНЕН** — срезанная фраза компаньона §2.14 стоит на месте (`14-api-contract/README.md:536`=`различать причины отказа клиент не может по замыслу`) · **(д) ИСПОЛНЕН** — клетка приложения А заполнена `unspecified` (`README.md:1376` — ⚠ первая редакция писала `:1391`, это строка ДРУГОГО приложения; испр. 20.08) · **(г) наполовину** — `unspecified` ратифицирован в каноне (`openapi.yaml:1784-1787`), открыта только ГРАНИЦА ступеней замечаний (две ступени при девяти рангах движка) → это строка **148** · **(и)** закрыт полем `stop_requested` (D39.152) · (л)(м)(н)(о) закрыты контрактной сессией. **ОТКРЫТО РЕАЛЬНО:** ⚠ **(а) ИСПОЛНЕН 27.08 минором 0.5.0 (D39.161)** — канон получил предупреждение о конверте вне `/v0`; открытым по строке остаётся (к). Прежний текст пункта: (а) канон-половина про `/auth` — компаньон поверхность описывает (§2.14), а канон говорит только «Signing in is not part of this surface» (`openapi.yaml:35-36`) и НЕ предупреждает, что конверт вне `/v0` может прийти БЕЗ обязательного `code`; сгенерированный по канону клиент об это спотыкается · (з) ⚠ **СНЯТО КАК РАТИФИКАЦИЯ 22.08 — конфликта моделей НЕТ, я нёс причину и следствие наоборот.** Прежняя редакция писала: «D39.144 ратифицировал подпись = ОДИН акт, движок держит пер-термный гейт полноты, одно из двух должно уступить». Сквозная трассировка это опровергла КОДОМ: гейт проверяет полноту не ПОДПИСЕЙ, а ФАЙЛА решений, и ОДИН акт его снимает — `loadMinedDelta` штампует только `Source`, статус не трогает (`mining.go:855-867`) → опущенный статус дефолтится в `approved` (`membank/memseed.go:96`=`status = "approved"`) → `unsignedEngineSurfaces` выбрасывает лишь `Source=="mined" && Status!="approved"` (`mining.go:710-719`) ⇒ промоутнутые строки остаются сид-поверхностями, дельта пустеет, стоп снимается. Шапка файла говорит это прямым текстом. **Один файл со всем банком = один ОК.** Обход в платформе (`--verify-bank` не передаётся на resume) — костыль под ОТСУТСТВУЮЩУЮ ДОСТАВКУ, а не под расхождение моделей: как только доставка появится, флаг можно просто оставить включённым. Настоящий и единственный блокер темы — владение `book.yaml`, строка **199(а)**. Ратификации здесь НЕ нужно; остаётся снять обход одной боевой строкой (`platform/internal/runs/spawn.go`) и семью строками ставшего ложным обоснования — работа платформенного пака, не решение владельца · (к) ключ `project_db` в `book.yaml`: договорить, кто им владеет — шаблон оператора его не содержит, движок делает необязательным. ⚠ **Следствие «на штатном деплое банк не читается» ОПРОВЕРГНУТО живым кодом** (проверено при лендинге): акт 5 внёс фолбэк `.db` и назвал прежний отказ багом (`platform/internal/runner/artifacts.go:82-90`=`The key is OPTIONAL to the engine`); первая редакция этой строки повторила клейм хендоффа зоны, не сверив с деревом — остаток чисто договорной · **(л) остаток закрытой строки 148 — Приложение А компаньона.** ⚠ Сама 148 СНЯТА 21.08 по слову владельца: она держала мир, которого больше нет — «карта потеряна инцидентом 08.08, реконструкция у оркестратора №15, решить, брать ли черновиком». Реконструкция брать нечего: прежняя карта вела «причина движка → ФРАЗА», то есть предполагала, что фразу рисует СЕРВЕР, а батч 0.3.0 эту политику ОТМЕНИЛ — `Note` несёт машинный `code`, фразу рисует клиент, `Note.message` с провода снят (`14-api-contract/README.md:1511`=`Карта «причина → КОД контракта`). Реально пуста другая клетка: соответствие причин движка кодам замечаний, и правила её заполнения в компаньоне уже написаны (фраза по ДОККОММЕНТУ `disposition.go`, а не по имени константы — инверсия уже стоила двух фраз; класс 2 схлопывается в ОДИН код). Исполнитель — контрактная сессия, не оркестратор ⚠ **ПЕРЕ-ДИСПОЗИЦИЯ 27.08 (D39.158 п.7):** хвост (з) закрыт — «ратификации не нужно» отвечало на вопрос про один файл решений, не про флажок; движковый гейт полноты снят, не обойдён.| контракт/доки | скоро (следующее касание контракта, вместе с 200) | контрактная сессия | приёмка P7 (D39.153) | | 204 | **Движок не публикует причины флагов ДАННЫМИ — карта причин у платформы рукописная и расходится молча** (релей §7(в) хендоффа P7; зона платформы в чужой бэклог не пишет и просила строку сюда — `platform/docs/archive/platform-PROGRESS-P7.md:206`). Сегодня `platform/internal/ingest/notes.go` держит рукописную копию закрытого словаря ЧУЖОЙ зоны: 15 причин движка → коды замечаний контракта, сверено побайтно на 20.08 и верно СЕЙЧАС. Импортировать движок платформе нельзя (D39.85 — разные модули), поэтому расхождение появится молча в тот день, когда движок добавит причину: платформа отдаст `unspecified` и напишет ERROR в лог, то есть деградация честная, но карта протухнет незаметно. Лечение: движок публикует свой словарь причин артефактом-данными (тем же классом, что манифест и сайдкары банка), платформа читает его вместо копии. ⚠ Носителем был ТОЛЬКО регистр платформы (PD-246) — то есть обязательство к зоне движка жило внутри зоны автора; поймано ревьюером полноты выгрузки при лендинге | бэкенд | когда-нибудь (со следующим касанием эмиссии флагов) | отдельное решение | приёмка P7 (D39.153), релей §7(в) | | 205 | **Гейт якорей: ИСПОЛНЕН 21.08 — сверка по содержимому, двусторонняя дельта, объявленная слепота.** Был: проверял только существование файла и переполнение номера, поэтому дал ЗЕЛЁНЫЙ на четыре якоря, сломанных архивацией — а оркестратор на эту зелень опёрся вместо ручной сверки. Стало (`docs/scripts/counts.py`): якорь может нести ОПТ-ИН токен ожидания `` `путь:12-14`=`подстрока` ``, и такой якорь сверяется по содержимому; область расширена с `docs/` на `platform/docs` и `frontend/docs` (98 доков); из хука убран быстрый выход по «коммит не трогает docs/» — он ампутировал ЦЕЛЕВУЮ сторону, а цели у нас в основном КОД, то есть главный двигатель протухания приезжал ровно тем коммитом, на котором хук молчал (проверено ТЕСТ-КОММИТОМ, не ручным прогоном — норма D39.148). ⚠ **Разбор с внешним ревьюером снял три моих ошибки дизайна:** (1) у дельты ДВЕ роли, и путать их нельзя — ТРЕБОВАТЬ токен можно только на затронутых строках (иначе долг блокирует всех), а ПРОВЕРЯТЬ уже написанные токены дельта ограничивать не должна вовсе: токен был истинным при записи, значит упавший — гниль по построению; corpus-wide проверка схлопывает «пропущенное окно» (коммит без хука, `--no-verify`, свежий клон, проигнорированный warn) из «навсегда» в «до следующего прогона»; (2) окно диапазона `80-82,243` считалось как `min..max` — 164 строки, и токен, случайно живущий внутри, красил зелёным; теперь ОБЪЕДИНЕНИЕ отрезков; (3) литеральная сверка токена была машиной ложных тревог — фраза, разорванная переносом строки в цели, непроверяема в принципе, а будущая пере-вёрстка абзаца кричала бы «цель уехала» на невиновного; теперь ПРОБЕЛЬНАЯ нормализация (детерминированная и симметричная), разметка остаётся литеральной. ⚠ Мой довод «неудобство и есть механизм» был неверен: мой собственный случай был FALSE POSITIVE самого гейта — токен был истинным, цель не двигалась. **ОБЪЯВЛЕННАЯ СЛЕПОТА (печатается гейтом каждый прогон):** якорь БЕЗ токена — только существование и длина · подмена цели файлом с тем же путём ловится только токеном · цели читаются из ДЕРЕВА, поэтому чужой незакоммиченный WIP может транзиентно и уронить токен, и спасти · токен доказывает ссылочную свежесть, НЕ истинность фразы дока о цели · в ручном прогоне «дельта» = всё незакоммиченное кем угодно, поэтому ТРЕБОВАНИЕ токена живёт только под `--from-index`. **ОСТАТОК:** миграция корпуса на токен-форму органическая — токен требуется на ЗАТРОНУТЫХ строках, поэтому счёт растёт сам с каждым касанием. Числа здесь НЕ держим (первая редакция держала «4 из ~580» и протухла за сутки): текущий счёт токенов, якорей и доков гейт печатает КАЖДЫЙ прогон — `python3 docs/scripts/counts.py --lint` | оркестратор/доки | сделано 21.08, миграция органическая | исполнено; остаток гасится касаниями | аудит работы №18 + разбор с внешним ревьюером, 21.08 | | 207 | **Пол самосогласованности манифеста стоит только у материализатора, интейк тот же документ ПРИНИМАЕТ — и по такой книге можно ОПЛАТИТЬ прогон** (`PD-367`, вторая волна ревью P8-FIX). Манифест `{ChaptersTotal: 120, UnitsTotal: 400}` с пустым списком глав `Whole()` отвергает, а `books.Parse` заводит книгу `not_started` с `chapter_count=120` и пустым деревом; потолок считается ОТ `chapter_count`. **Очевидное лекарство опровергнуто исполнением самой зоной:** применить `Whole()` на интейке нельзя — вся батарея интейка ездит на манифестах без списка глав, контракт интейка это только счётчики. Значит решение не зонное: либо контракт интейка меняется, либо пол ставится в другом месте цепи | оркестратор → платформа | скоро | нужно решение (лекарство ломает запиненный контракт интейка) | D39.154 п.10, `PD-367` | | 208 | **Эскалационный хоп на Gemini ТИХО ТЕРЯЕТ инъекцию банка памяти — книга едет без канона имён** (живая проба полигона 22.08, подтверждена вендор-докой и пере-проверена оркестратором по коду). OpenAI-совместимый слой Gemini принимает РОВНО ОДНО системное сообщение, лишние выбрасывает МОЛЧА — без ошибки, при `finish=stop` и внешне правильном переводе. Замер полигона на одном и том же системном тексте 7970 знаков: одним сообщением → `prompt_tokens` 2994 и маркерная инструкция ИСПОЛНЕНА, двумя → 535 токенов и инструкция НЕ исполнена. Механизм вендорский, поэтому уход на нативный API НЕ лечит: `systemInstruction` там — один объект `Content`. Наша сторона: `backend/internal/pipeline/render.go` штатно строит ВТОРОЕ системное сообщение (инъекция глоссария/STM), `backend/internal/llm/httpllm.go` `toOpenAIMessages` копирует сообщения дословно, склейки под Gemini нет нигде — а `gemini-3.1-pro-preview` стоит в ратифицированной цепочке эскалации ВО ВСЕХ четырёх арм-конфигах. ⇒ любой хоп на Gemini при непустой инъекции теряет глоссарий, и **это невидимо гейтам годности**. ⚠ Класс в проекте УЖЕ решён рядом и не обобщён: `provider_anthropic.go` сворачивает ведущий system-префикс в одно поле и падает громко на system посреди диалога. Оговорка D22.3 «экспозиция Ф1 нулевая» держится на МАРШРУТЕ (до Gemini в дефолте не доходят), а не на коде — то есть на одном включённом `budget_usd` от дефекта в проде. Полигон у себя починил склейкой по вендору (`eval/dovodka/rol.py`), в движок не лез; квирк записан в `00-provider-quirks.md` | бэкенд | **скоро (до первого платного прогона с ненулевым `budget_usd` эскалации)** | склейка системных сообщений для Gemini в транспорте + пин | пинг полигона №19 (22.08), вендор-дока `ai.google.dev/api/generate-content` | | 209 | **Риг живых проб не может залогировать тела запросов ни при каких env** (находка бэкенд-пака честности 17.08, оставшаяся без носителя до аудита доков 22.08): `LOG_LLM_BODIES=1`+`LOG_LEVEL=debug` объявлены носителем тел, но ключ читается из `obs.ReqInfo` контекста, а `live_reprobe_test.go` строит ctx БЕЗ `WithReqInfo` — гейт `obs/logging.go` не пропускает. Обход, которым пользовались: ручной дамп сообщений в файл. Цена — каждая живая проба, которой нужно прочитать провод глазами, платит за обход заново | бэкенд | когда-нибудь (следующее касание рига живых проб) | малое касание (`WithReqInfo` в риге) + пин | пак честности 17.08, вынесено аудитом доков 22.08 | | 210 | **Род НЕ ПРОИЗВОДИТСЯ авто-путём вовсе — механизм доставки построен, а данных для него нет** (найдено трассировкой цепи банка 22.08, сверено кодом оркестратором; полигон вскрыл то же независимо: «в банке НЕТ ПОЛЕЙ ПОЛА»). Доставка рода на провод ПОСТРОЕНА и работает: `membank/memory.go` кладёт `genderConstraintNote` в черновую инъекцию — но ТОЛЬКО для строк со статусом `Confirmed`, по прямой директиве владельца D39.21 «род должен доезжать». Данных ей никто не даёт: промпт терминолога возвращает РОВНО ТРИ поля (`backend/prompts/zh-ru/terminologist.md`: термин · перевод · уверенность 0–100) — поля рода в ответе НЕТ; майнер берёт `gender` исключительно из СИДА (`internal/miner/miner_emit.go` `seedMeta`, `meta.gender = e.Gender`), эмитируемые кандидаты его не несут. ⇒ **на любой книге без ручного сида ось рода мертва по построению**, и «плывущий род» — это не дефект рендера, а отсутствие входных данных. Прямо бьёт по цели №1: реестр памяти C3 называет русский глагол прош. вр. при скрытом роде «механическим спойлер-каналом» и «жалобой №1 читателей MTL» (строка 82). **Развилка, которую надо назвать до стройки:** добавлять ли поле рода в ответ терминолога (двигает провод ⇒ ТОЛЬКО в вайр-батч 182) ЛИБО выводить род отдельным дешёвым проходом, ЛИБО признать род сид-только свойством и гейтить им качество явно. Родня 82 (морфо-гейт рода) и 178 (калибровка `sense`). ⚠ **ДОПОЛНЕНО 23.08 (№19):** замка ДВА и они независимы — род рендерится только при `Disp == Confirmed` (`membank/memory.go:705-713,823-827`=`Mirrors the editor block's confirmed-only gender`), а `mining.go:676` запрещает авто-банку статус `approved`. Род НЕ единственное осиротевшее поле: `Sense`/`Speech`/`Decl` тоже приходят только из сида, авто-продюсер есть лишь у `Dst` (терминолог) и `Type` (классификатор). ⚠ Поле `Gender` в схеме сида УЖЕ ЕСТЬ (`internal/seed/seed.go:71`=`Gender string`) — ось мертва в ПИСАТЕЛЯХ, не в формате. ⚠ **И тесты слепы ПО ПОСТРОЕНИЮ** (пере-проверено 23.08): все gender-кейсы конструируют род РУКАМИ, то есть зелёная батарея не отличает «производитель работает» от «производителя нет»; гейта на дрейф рода в движке нет | бэкенд (+полигон на замер) | **скоро (цель №1; правка провода — вайр-батчем 182)** | развилка → решение → правка промпта/прохода + замер на прогоне с редактурой | трассировка цепи банка 22.08, D39.21, C3 реестра памяти | -| 211 | **⛔ Движок на SaaS НЕ ПОЛУЧАЕТ провайдерских ключей — блокер живого прогона (202).** Прод-путь `systemd-run --user` (`platform/internal/runner/runner.go:136,149`=`"systemd-run", args...`), окружение юнита = окружение systemd-менеджера + `--setenv` из `s.Env`, а платформа кладёт туда РОВНО `TM_TRACE_ID` (`runs/spawn.go:168`=`Env: engineEnv(engineStreamID(`, `engineEnv` `:268`). Замысел записан комментарием `:265-267` («движок читает ключи из .env рядом со своим book.yaml»), движок так и делает (`backend/cmd/tmctl/main.go:202`=`loadDotEnv(filepath.Join(filepath.Dir(inv.cfgPath), ".env")`; `cmd.Dir = workdir` — `runner/engine.go:176`=`cmd.Dir = workdir`, оба пути в ОДИН файл), **а кладёт туда `.env` НИКТО**: в каталоге книги платформа создаёт только `book.yaml` (`books/render.go:109`=`os.O_CREATE ⚠ **ДВИЖКОВАЯ ПОЛОВИНА ПОСТРОЕНА 27.08 (лендинг `d1eb8a9`, D39.158), строка об этом не знала — поймано свипом по норме §3 п.8 при ответе владельцу 27.08.** Движок принимает путь к файлу ключей аргументом (`backend/cmd/tmctl/dotenv.go:46,63`, флаг `--keys-file`, пин `cmd/tmctl/keysfile_test.go`) — то есть класть ключи в окружение юнита больше не обязательно. **Открыт ПЛАТФОРМЕННЫЙ конец:** греп по `platform/` даёт НОЛЬ упоминаний `--keys-file` — раннер его не передаёт. Значит блокер живого прогона (строка 202) снят наполовину и закрывается пунктом **(2в)** очереди D39.156, а не отдельной работой |os.O_EXCL`, O_EXCL) и исходник (`books/books.go:259-264`=`os.O_CREATE|os.O_EXCL`); писателя `.env` в `platform/internal/` ноль. ⚠ **Почему пережило все батареи — ловушка паритета dev/prod:** дев-супервизор запускает движок обычным процессом с `Env=nil` и наследует окружение ПЛАТФОРМЫ, что обещает его же докстринг (`ingest/supervisor.go:35-36,65-67`=`Provider keys reach the engine through it`) — стенд работает, прод голодает, гейты гоняют дев-путь. **Форма фикса (консилиум 22.08):** путь к файлу ключей аргументом у `translate` (читающим $0-глаголам ключи запрещены — `dotenv.go:53-55`), деплой-файл ПЕРВЫМ в цепочке без перекрытия, дев-супервизор на тот же механизм. Довод формы: ключи обязаны покинуть каталог пользовательского контента — сегодня бэкап/экспорт каталога книги несёт секреты. ⚠ Ключи ОБЩИЕ на всех пользователей (слово владельца 22.08); BYOK не исключён позже и ложится в ту же форму пер-книжным путём | бэкенд + платформа | **блокер строки 202** | движковый + платформенный пак | консилиум шва 22.08, пере-проверено оркестратором №19 | +| 211 | **⛔ Движок на SaaS НЕ ПОЛУЧАЕТ провайдерских ключей — блокер живого прогона (202).** Прод-путь `systemd-run --user` (`platform/internal/runner/runner.go:137`=`"systemd-run", args...`), окружение юнита = окружение systemd-менеджера + `--setenv` из `s.Env`, а платформа кладёт туда РОВНО `TM_TRACE_ID` (`runs/spawn.go:168`=`Env: engineEnv(engineStreamID(`, `engineEnv` `:268`). Замысел записан комментарием `:265-267` («движок читает ключи из .env рядом со своим book.yaml»), движок так и делает (`backend/cmd/tmctl/main.go:202`=`loadDotEnv(filepath.Join(filepath.Dir(inv.cfgPath), ".env")`; `cmd.Dir = workdir` — `runner/engine.go:186`=`cmd.Dir = workdir`, оба пути в ОДИН файл), **а кладёт туда `.env` НИКТО**: в каталоге книги платформа создаёт только `book.yaml` (`books/render.go:109`=`os.O_CREATE`) ⚠ **ДВИЖКОВАЯ ПОЛОВИНА ПОСТРОЕНА 27.08 (лендинг `d1eb8a9`, D39.158), строка об этом не знала — поймано свипом по норме §3 п.8 при ответе владельцу 27.08.** Движок принимает путь к файлу ключей аргументом (`backend/cmd/tmctl/dotenv.go:46,63`, флаг `--keys-file`, пин `cmd/tmctl/keysfile_test.go`) — то есть класть ключи в окружение юнита больше не обязательно. **Открыт ПЛАТФОРМЕННЫЙ конец:** греп по `platform/` даёт НОЛЬ упоминаний `--keys-file` — раннер его не передаёт. Значит блокер живого прогона (строка 202) снят наполовину и закрывается пунктом **(2в)** очереди D39.156, а не отдельной работой |os.O_EXCL`, O_EXCL) и исходник (`books/books.go:259-264`=`os.O_CREATE|os.O_EXCL`); писателя `.env` в `platform/internal/` ноль. ⚠ **Почему пережило все батареи — ловушка паритета dev/prod:** дев-супервизор запускает движок обычным процессом с `Env=nil` и наследует окружение ПЛАТФОРМЫ, что обещает его же докстринг (`ingest/supervisor.go:35-36,65-67`=`Provider keys reach the engine through it`) — стенд работает, прод голодает, гейты гоняют дев-путь. **Форма фикса (консилиум 22.08):** путь к файлу ключей аргументом у `translate` (читающим $0-глаголам ключи запрещены — `dotenv.go:53-55`), деплой-файл ПЕРВЫМ в цепочке без перекрытия, дев-супервизор на тот же механизм. Довод формы: ключи обязаны покинуть каталог пользовательского контента — сегодня бэкап/экспорт каталога книги несёт секреты. ⚠ Ключи ОБЩИЕ на всех пользователей (слово владельца 22.08); BYOK не исключён позже и ложится в ту же форму пер-книжным путём | бэкенд + платформа | **блокер строки 202** | движковый + платформенный пак | консилиум шва 22.08, пере-проверено оркестратором №19 | | 213 | **Платформа дублирует движковую конвенцию пути и знает суффикс чужого артефакта.** `platform/internal/runner/artifacts.go:65-95`=`neither project_db nor book_id` парсит `book.yaml` нестрогим декодером ради двух ключей и при пустом `project_db` САМА вычисляет `filepath.Join(workdir, book_id+".db")`, повторяя `backend/internal/config/book.go:189`=`b.ProjectDB = filepath.Join(dir, b.BookID+".db")`; суффикс `.bank.json` зашит на `:37`. Смена дефолта в движке тихо уводит чтение банка платформой на несуществующий путь. ⚠ Свип ВСЕЙ границы движок↔платформа (консилиум 22.08) дал ровно ОДНО такое место плюс тривиальность: дев-супервизор хардкодит `"book.yaml"` мимо константы `ConfigFile` (`ingest/supervisor.go:140`=`"status", "--config"`). Прочее знание платформы о движке — ратифицированные каналы, не гниль. **Фикс:** движок отдаёт путь артефакта аддитивно (`status --json` либо ответ нового глагола), платформа выкидывает свой `projectDB()`. ⚠ Пере-именование банк-экспорта в фикс-имя рядом с `events.jsonl` — ЛОМАЮЩЕЕ, его место в окне строки **161**, НЕ здесь | бэкенд + платформа | скоро (в те же паки) | движковый + платформенный пак | консилиум шва 22.08 | -| 214 | **Подпись банка не оставляет на банке НИ ОДНОГО следа.** Единственная долговечная запись акта подписи — булев `bank_released` на прогоне (`platform/internal/pgstore/runs.go:844`=`bank_released = bank_released or $2`): кто и когда подписал, что именно вошло в подписанный набор — не хранится нигде. Следствие: пост-фактум ответить «почему в книге этот перевод термина» нельзя, а при пере-нарезке или пере-прогоне восстановить намерение человека не из чего. Всплыло трассировкой цепи банка 22.08, подтверждено пере-проверкой | платформа + бэкенд | когда-нибудь (с трубой доставки правок, D39.156) | отдельное решение о провенансе подписи | трассировка цепи банка 22.08, пере-проверено №19 | +| 214 | **Подпись банка не оставляет на банке НИ ОДНОГО следа.** Единственная долговечная запись акта подписи — булев `bank_released` на прогоне (`platform/internal/pgstore/runs.go:851`=`bank_released = bank_released or $2`): кто и когда подписал, что именно вошло в подписанный набор — не хранится нигде. Следствие: пост-фактум ответить «почему в книге этот перевод термина» нельзя, а при пере-нарезке или пере-прогоне восстановить намерение человека не из чего. Всплыло трассировкой цепи банка 22.08, подтверждено пере-проверкой | платформа + бэкенд | когда-нибудь (с трубой доставки правок, D39.156) | отдельное решение о провенансе подписи | трассировка цепи банка 22.08, пере-проверено №19 | | 215 | **$0-точечный репин движка работает ТОЛЬКО под `--resnapshot`, а платформа этого флага не передаёт.** Механизм пере-привязки незатронутых юнитов при сдвиге банка построен (`backend/internal/pipeline/stagerun.go:88-107`=`POINTWISE RE-EDIT`) и он и делает подпись термина доступной по цене; но с платформы он недостижим — она зовёт `translate` без флагов сверх потолка и `--verify-bank`. ⇒ на SaaS правка термина сегодня стоила бы полной пере-редактуры, а не дельты. Родня строки 199(б) (нулевой набор consent-флагов) | платформа + бэкенд | когда-нибудь (с платформенным паком шва) | отдельное решение | трассировка цепи банка 22.08, пере-проверено №19 | | 216 | **Подпись банка, halt на потолке и резюм после пополнения не проверялись против ЖИВОГО движка ни разу.** Проверки этих путей в батарее платформы стоят на фейках; единственный живой прогон движка в батарее — не по этим маршрутам. То есть три несущих сценария продукта держатся на договорённости о поведении чужой стороны, а не на замере. ⚠ Гейтится тем же, чем и живой прогон книги (строка 202): холодный прогон движка | платформа | когда-нибудь (с 202) | живой прогон по трём маршрутам | трассировка цепи банка 22.08, пере-проверено №19 | -| 217 | **Скрипты полигона захардкодили АБСОЛЮТНЫЕ пути двух корней и не работают на этой машине вовсе.** ДВА РАЗНЫХ слома. **Корень репо** — смена пользователя: литерал в 121 файле, из них 104 дословно `REPO = Path("/home/ubuntu/projects/textmachine")`, портабельных форм всего 5. **Корень книг** — оба события порознь: литерал `/home/ubuntu/books` 86 файлов (пользователь), портабельный `Path.home()/"books"` 89 файлов (переезд каталога; на прежних машинах работал). Объединение 173 файла. Пользователь теперь `ubuntu-26`, книги переехали в `<репозиторий>/books` ⇒ ломается и то и другое, включая ЖИВОЙ гейт фазы Д `eval/conformance.py:48`=`REPO = Path("/home/ubuntu/projects/textmachine")`. Живых носителей 26 по корню книг (среди них портабельная форма преобладает 20:6) плюс корневые гейты и сборщики корпусов; остальное — скрипты закрытых эксп-12–16, их владелец велел ВЫБРОСИТЬ, а не чинить (D39.157 п.4а). Форма правки — за зоной; приор оркестратора: один модуль путей, корень резолвится МАРКЕРОМ вверх по дереву (движок так и делает — `backend/internal/miner/miner_parity_test.go:29`=`finds the repository root by MARKER`), книги от него, обе ручки перекрываются env | полигон | скоро (блокирует любой прогон зоны) | пак путей полигона | переезд машины 24.08, №19 | +| 217 | **Скрипты полигона захардкодили АБСОЛЮТНЫЕ пути двух корней и не работают на этой машине вовсе.** ДВА РАЗНЫХ слома. **Корень репо** — смена пользователя: литерал в 121 файле, из них 104 дословно `REPO = Path("/home/ubuntu/projects/textmachine")`, портабельных форм всего 5. **Корень книг** — оба события порознь: литерал `/home/ubuntu/books` 86 файлов (пользователь), портабельный `Path.home()/"books"` 89 файлов (переезд каталога; на прежних машинах работал). Объединение 173 файла. Пользователь теперь `ubuntu-26`, книги переехали в `<репозиторий>/books` ⇒ ломается и то и другое, включая ЖИВОЙ гейт фазы Д `eval/conformance.py:48`=`REPO = Path("/home/ubuntu/projects/textmachine")`. Живых носителей 26 по корню книг (среди них портабельная форма преобладает 20:6) плюс корневые гейты и сборщики корпусов; остальное — скрипты закрытых эксп-12–16, их владелец велел ВЫБРОСИТЬ, а не чинить (D39.157 п.4а). Форма правки — за зоной; приор оркестратора: один модуль путей, корень резолвится МАРКЕРОМ вверх по дереву (движок так и делает — `backend/internal/miner/miner_parity_test.go:29`=`derived from the repository MARKER`), книги от него, обе ручки перекрываются env | полигон | скоро (блокирует любой прогон зоны) | пак путей полигона | переезд машины 24.08, №19 | | 219 | **Line-якоря в тела D-лога обречены съезжать, и механизм именно в дисциплине эррат.** Эрраты вписываются в КАРТУ ШАПКИ (append-only, D23.3), то есть в начало файла, — значит каждая эррата сдвигает номера строк ВСЕХ тел ниже, и любой якорь вида `05-decisions-log.md:NNN` умирает молча. ⚠ Замерено на себе 27.08: три эрраты за сессию убили якоря в `17-seam-inbound-law.md:120` и `research/25-seam-cold-review.md:5` (оба целили в `:232`, тело уехало на `:239`). Лечение — не пере-нацеливание (оно повторится через эррату), а СМЕНА ФОРМЫ якоря: в тела D-лога целиться номером ноты (`^## D39.106`), который стабилен навсегда, а не строкой. ⚠ Строка ОБЪЯВЛЕНА в CURRENT-STATE 24.08 и до 27.08 в таблице НЕ СУЩЕСТВОВАЛА — очередь три дня ссылалась на носитель, которого нет; поймано свипом якорей при выдаче контрактного минора | оркестратор | скоро (растёт с каждой эрратой) | `--lint` учит форму `файл:^## D<номер>`, живые якоря в D-лог переводятся на неё | ревью доков 24.08, механизм и отсутствие носителя — 27.08, D39.160 | -| 220 | **Репо-широкий гейт доков бутстрапится пакетным менеджером ОДНОЙ зоны, и та заморожена.** Диспетчер `.git/hooks/pre-commit` зоно-нейтрален по построению (`for hook in */scripts/githooks/pre-commit`, `#!/bin/sh`, node не нужен) и судит `CLAUDE.md` + `docs/**` + `platform/docs/**` + `frontend/docs/**` (`docs/scripts/counts.py:471`=`ROOT / "CLAUDE.md"`), но единственный его УСТАНОВЩИК — `frontend/scripts/githooks/install.mjs`, вызываемый ключом `prepare` из `frontend/package.json`. Охват и бутстрап не совпадают ⇒ инструмент решает свою задачу не в полной мере. Проверено исполнением 24.08: на этой машине `.git/hooks/` содержал только `*.sample`, гейт МОЛЧАЛ, ничего не проверив, и поймал первые четыре дефекта только после ручной установки; сессия бэкенда или доков `npm install` не делает никогда. Приор: зоно-нейтральный установщик в `docs/scripts/githooks/` как ЕДИНСТВЕННЫЙ писатель диспетчера, `prepare` фронта зовёт его — фронтовая половина выписана пингом №19 в зонный журнал фронта. ⚠ Ручная установка — временная мера, пока строка открыта; записывать её как порядок работы ЗАПРЕЩЕНО (это ровно тот обход, цену которого называет шапка `CLAUDE.md`) | доки + фронт | скоро | зоно-нейтральный бутстрап + правка `prepare` при разморозке | аудит переезда 24.08, №19 | +| 220 | **Репо-широкий гейт доков бутстрапится пакетным менеджером ОДНОЙ зоны, и та заморожена.** Диспетчер `.git/hooks/pre-commit` зоно-нейтрален по построению (`for hook in */scripts/githooks/pre-commit`, `#!/bin/sh`, node не нужен) и судит `CLAUDE.md` + `docs/**` + `platform/docs/**` + `frontend/docs/**` (`docs/scripts/counts.py:500`=`ROOT / "CLAUDE.md"`), но единственный его УСТАНОВЩИК — `frontend/scripts/githooks/install.mjs`, вызываемый ключом `prepare` из `frontend/package.json`. Охват и бутстрап не совпадают ⇒ инструмент решает свою задачу не в полной мере. Проверено исполнением 24.08: на этой машине `.git/hooks/` содержал только `*.sample`, гейт МОЛЧАЛ, ничего не проверив, и поймал первые четыре дефекта только после ручной установки; сессия бэкенда или доков `npm install` не делает никогда. Приор: зоно-нейтральный установщик в `docs/scripts/githooks/` как ЕДИНСТВЕННЫЙ писатель диспетчера, `prepare` фронта зовёт его — фронтовая половина выписана пингом №19 в зонный журнал фронта. ⚠ Ручная установка — временная мера, пока строка открыта; записывать её как порядок работы ЗАПРЕЩЕНО (это ровно тот обход, цену которого называет шапка `CLAUDE.md`) | доки + фронт | скоро | зоно-нейтральный бутстрап + правка `prepare` при разморозке | аудит переезда 24.08, №19 | | 49 | **Этапы Б+В спеки D15.2** (content-addressed resume / `guard_hash` — D39.31 сознательно не строил) ; этап В = tmctl export-контракт D29.1 (annot-v1 · цвет-мап+rollup · политика красных fail-closed) + операторский протокол-документ релиза гендер-твиста (D5.1): сам механизм УЖЕ построен — until_ch-правка → точечный re-edit со сметой и $0-пере-пином (`repin.go`/`rebill.go`, D39.42 п.5/D39.45) | бэкенд | скоро (ПОДТЯНУТА D39.81: annot-v1 = источник замечаний для фронта — критический путь подключения) | отдельное решение (annot-половина — по контракту 14, D39.99) | D39.34(4), D33 п.5, D39.81 | | 49а | **ALTER-шаги миграций v8–v14 не идемпотентны вопреки шапке `migrate.go:9-11`** (полу-применённая БД не сходится; счётчик версий скрывает) — находка критика полноты пака-19 | бэкенд | когда-нибудь | отдельное решение | D39.56, PACK19_BUILD §6.9 | | 50 | F3-остаток идемпотентности | бэкенд | когда-нибудь | отдельное решение | D39.34(4) | diff --git a/docs/architecture/17-seam-inbound-law.md b/docs/architecture/17-seam-inbound-law.md index cd071e79..1ba96077 100644 --- a/docs/architecture/17-seam-inbound-law.md +++ b/docs/architecture/17-seam-inbound-law.md @@ -42,7 +42,7 @@ **(б) фикс-имена ратифицированной конвенции шва** — `events.jsonl` никто платформе не «сообщает», это константа (`platform/internal/ingest/tail.go:17`=`const JournalFile = "events.jsonl"`, D39.106); без этого выноса буква ломает главный канал самого шва. *Прецеденты:* потолок аргументом (D39.110 п.2б → `--ceiling-usd`, D39.122) · `--verify-bank` - флагом (`platform/internal/runner/engine.go:83-87`=`func TranslateArgs(workdir string, verifyBank bool`) · дефолт `project_db` по конвенции каталога + флагом (`platform/internal/runner/engine.go:90`=`func TranslateArgs(workdir string, verifyBank bool`) · дефолт `project_db` по конвенции каталога (`backend/internal/config/book.go:189`=`b.ProjectDB = filepath.Join(dir, b.BookID+".db")`) · конвенционное опциональное чтение `.auto-bank.yaml` (`backend/internal/pipeline/mining.go:617`=`.auto-bank.yaml`). @@ -56,7 +56,7 @@ *Почему жёстко:* движок пере-читает сид ПОСРЕДИ прогона (`mining.go:221`), запись в живой прогон въехала бы в снапшот недетерминированно. Потолок посреди прогона менять НЕЛЬЗЯ — он едет со стартом и после не меняется, лекарство — новый прогон - (`platform/internal/runs/reconcile.go:1222-1227`=`answering 202 with the run in the state`). Дверь «менять потолок файлом» закрыта; + (`platform/internal/runs/reconcile.go:1228`=`answering 202 with the run in the state`). Дверь «менять потолок файлом» закрыта; не выдумывать её заново из `research/25`. 3. **Каждый JSON-выход глагола несёт версию документа.** Сегодня асимметрия: `tm-bank-v1` diff --git a/docs/research/25-seam-cold-review.md b/docs/research/25-seam-cold-review.md index 8475ed1c..aed60fcb 100644 --- a/docs/research/25-seam-cold-review.md +++ b/docs/research/25-seam-cold-review.md @@ -2,9 +2,9 @@ > ⚠ **ЭРРАТА D39.156 (23.08): «поднятие потолка ФАЙЛОМ через atomic rename» (пункт ниже, дословно `потолка — файлом через atomic rename на границах юнитов`) НИКОГДА НЕ РАТИФИЦИРОВАЛОСЬ.** > Отчёт относит этот механизм к носителям решения D39.106 — в теле D39.106 его НЕТ вовсе (проверено грепом по слайсу -> живого тела D39.106 — `../architecture/05-decisions-log.md:239`=`ШОВ: прогон переживает деплой платформы`; первый хит темы — только в D39.110. ⚠ Испр. 23.08: прежняя редакция ссылалась на слайс `archive/architecture/05-decisions-D39-106-123.md`, где тела D39.106 НЕТ — греп по нему подтвердил бы эррату вакуумно). Строка была КАНДИДАТОМ панели, +> живого тела D39.106 — `../architecture/05-decisions-log.md:241`=`ШОВ: прогон переживает деплой платформы`; первый хит темы — только в D39.110. ⚠ Испр. 23.08: прежняя редакция ссылалась на слайс `archive/architecture/05-decisions-D39-106-123.md`, где тела D39.106 НЕТ — греп по нему подтвердил бы эррату вакуумно). Строка была КАНДИДАТОМ панели, > а живой канон решил ось иначе: **потолок едет со стартом прогона и после не меняется**, лекарство при упоре — НОВЫЙ прогон -> (`platform/internal/runs/reconcile.go:1222-1227`=`answering 202 with the run in the state`). ⚠ Испр. 23.08: прежняя редакция звала «строку 69», но вставка самого баннера сдвинула нумерацию — 69-я строка теперь несёт ЖИВОЙ денежный механизм («холд не освобождается по выходу юнита»), и исполнение прежней инструкции пометило бы отвергнутым НЕ ТО. Номера строк здесь больше не называем, только подстроку. Читать НАЗВАННЫЙ ВЫШЕ пункт как отвергнутого +> (`platform/internal/runs/reconcile.go:1228`=`answering 202 with the run in the state`). ⚠ Испр. 23.08: прежняя редакция звала «строку 69», но вставка самого баннера сдвинула нумерацию — 69-я строка теперь несёт ЖИВОЙ денежный механизм («холд не освобождается по выходу юнита»), и исполнение прежней инструкции пометило бы отвергнутым НЕ ТО. Номера строк здесь больше не называем, только подстроку. Читать НАЗВАННЫЙ ВЫШЕ пункт как отвергнутого > кандидата, а не как обязательный механизм; закон входной двери шва (`../architecture/17-seam-inbound-law.md` п.2) это фиксирует: > посреди прогона внутрь идёт ТОЛЬКО сигнал.