diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 6c602669..315b59ca 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -141,7 +141,7 @@ | 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 | -| 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`, то есть новый минор; пока фронт заморожен, минор бесплатен | контракт → платформа + бэкенд | скоро (следующий минор контракта) | контрактная сессия + платформенная половина; вопрос движку о знаменателе майнинга | слово владельца 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) |