472 lines
62 KiB
Markdown
472 lines
62 KiB
Markdown
# Слайс зонного журнала: эра пака P8-FIX (21–22.08.2026)
|
||
|
||
> ⚠ **Архив. Инструкции отсюда НЕ исполнять, при онбординге НЕ читать** — только по конкретной ссылке.
|
||
> Вынесено оркестратором №18 22.08 при актуализации доков. Состояние зоны — шапка живого
|
||
> `../platform-PROGRESS.md`; дефекты — `../DEFECT_REGISTER.md`; правила, пережившие пак, —
|
||
> `../STACK_DECISIONS.md`; ратификация — **D39.154** в `docs/architecture/05-decisions-log.md`.
|
||
>
|
||
> **Что этот пак сделал:** закрыл фикс-лист приёмки P7 (пункты 1–7 и 9–13) и врезанный первым
|
||
> блокер свипа `PD-169` — голодание, из-за которого расчёт денег вставал для ВСЕЙ инсталляции.
|
||
> Плюс спил пер-термного write-пути банка по слову владельца (`PD-370`, ратификация D39.144).
|
||
> Лендинг — `31f1f82`.
|
||
>
|
||
> ⚠ **Что из этого отчёта ПЕРЕНЕСЕНО в долговечные носители и там первично:**
|
||
> инвентарь каналов шва с движком → `../STACK_DECISIONS.md` §«Инвентарь каналов движка» (и там он
|
||
> ПОЛНЕЕ: дописан канал текста `tmctl export --json --pairs`, которого в этой таблице не было) ·
|
||
> развилка владения `book.yaml` с четырьмя вариантами → строка **199(а)** единого бэклога ·
|
||
> поимённый разбор канона под минор 0.5.0 → строка **206** · граница sqlc (41 запрос) → `BACKLOG.md`
|
||
> **П-19** · ловушка `.check.log` и третий гейт батареи → `PD-374`.
|
||
|
||
- **P8-FIX — ФИКС-ЛИСТ ПРИЁМКИ P7 ЗАКРЫТ. Дерево готово к приёмке и лендингу (22.08).**
|
||
Батарея: 18 пакетов, exit 0, **скипов 0**, линтер 0 issues, скипов 0, тестов **561** (HEAD 532). Регистр —
|
||
**370** строк, открытых **68** (14 minor, 53 info, **1 major**) — сверено командой на момент сдачи
|
||
(`python3 docs/scripts/counts.py --check`, exit 0; `--lint` exit 0; ⚠ вес регистра этим скриптом НЕ
|
||
проверяется — числа сняты его печатью, а не гейтом). **Открытый major — `PD-370`**, контрактная
|
||
половина спила пер-термной подписи банка: она не работа зоны, закрывать её здесь было бы подгонкой
|
||
под критерий приёмки. Миграция пака: **00025**. Дерево не закоммичено и не застейджено.
|
||
**Удалённых имён тестов ТРИ, и все три — по решению владельца 22.08** (сверено исполнением, не
|
||
памятью: имена из `git show HEAD:<файл>` по всем `_test.go` против дерева):
|
||
`TestABadDecisionRefusesTheWholeBatchAndNamesTheItem` · `TestAnUnknownTermRefusesTheBatch` ·
|
||
`TestTheBankIsReplacedWhileTheDecisionsSurvive` — они описывали пер-термную подпись банка, которую
|
||
`D39.144` отменил, а этот пак снял из кода (`PD-370`). Тест снятого кода — не потеря покрытия;
|
||
третий переписан на СОХРАНИВШЕЕСЯ свойство (`TestTheBankIsReplacedFromTheEnginesReadOut`).
|
||
Плюс один ПЕРЕИМЕНОВАН по факту изменения смысла (`…GivesTheHoldBackOnlyWhenAsked` →
|
||
`…AlwaysGivesTheHoldBack`, `PD-361`) — имя заведено этим же паком.
|
||
⚠ **Пинг оркестратору №1 — ЕГО зона, сверить при лендинге:** `docs/architecture/05-decisions-log.md:542`
|
||
держит якорь на `internal/pgstore/perf_test.go:13` с токеном «96%». Пак ЭТОТ комментарий правил
|
||
(`PD-366`: он лгал про свой же бенчмарк), токен я сохранил намеренно и `--lint` зелёный — но док
|
||
чужой, поэтому говорю, а не считаю вопрос закрытым.
|
||
⚠ **Пинг оркестратору №2 — попутная находка ВНЕ зоны пака:** `PD-369` / `П-21` — `ClaimIdempotency`
|
||
под конкуренцией отвечает 500 (1–2 падения на ~80 прогонов собственного теста). Файл в диффе пака
|
||
не участвует, не трогал.
|
||
⚠ **Пинг оркестратору №3 (`PD-370`, требует контрактной сессии):** канон 0.4.0 несёт пер-термный
|
||
`BankDecision{action: approve|decline}`, а `D39.144` эту модель отменил 16.08 — подписывается весь
|
||
банк одним ОК. Зона канон исполняет, снять в одностороннем порядке не может. Правка канона →
|
||
правка зоны, в этом порядке.
|
||
⚠ **Сдача идёт ПОСЛЕ второй волны ревью**, которая нашла в самом паке блокер (`PD-353`) и два
|
||
дефекта, делавших его собственные механизмы недостижимыми (`PD-354`, `PD-355`) — раздел «Волна 2».
|
||
|
||
### Записка-план по §4 промта: пункт → где исполнено / отказ с причиной
|
||
|
||
| Пункт | Исход | Где |
|
||
|---|---|---|
|
||
| **Блокер свипа** (врезан первым) | **исполнен четырьмя механизмами** | ниже, отдельным разделом; `PD-169` пере-открыт и закрыт заново |
|
||
| 1. `ContractVersion` 0.3.0 при формах 0.4.0 | исполнен | `internal/httpapi/capabilities.go` + гейт `internal/gates/contract_test.go` (`PD-328`) |
|
||
| 2. Пины на два поля `EventBase` | исполнен, посадка сперва воспроизведена | `internal/pgstore/events_test.go` (`PD-329`) |
|
||
| 3. Бюджет попыток материализации | исполнен | `internal/readmodel/readmodel.go`, миграция 00025 (`PD-330`) |
|
||
| 4. sqlc + гейт разбора SQL | **гейт построен; sqlc — граница названа, решение владельца** | `internal/pgstore/sqlgate_test.go`; разбор — `PD-44`, строка бэклога **П-19** |
|
||
| 5. `PD-297` (round-trip под блокировкой) | **отказ с причиной** | нечем и не на чем мерить — ниже |
|
||
| 6. Труба доставки решений банка (строка 199а) | **не строил, сдаю вопросом** — как промт и требует | ниже, отдельным разделом |
|
||
| 7. Мусор: задвоенная строка доккомментария | исполнен | `internal/books/books.go` `canTranslate` (`PD-335`) |
|
||
| 8. Ревью-пак четырёх осей | **НЕ мой** — заказ пака P8-REVIEW | — |
|
||
| 9. Один носитель на факт для замера 00016→00022 | исполнен, с названным расхождением с буквой пинга | `internal/pgstore/perf_test.go` (`PD-336`) |
|
||
| 10. Пол на пустой манифест у материализатора | исполнен | `internal/ingest/manifest.go` `Whole()`, `internal/readmodel/readmodel.go` (`PD-332`) |
|
||
| 11. `PD-327` стоит open при ратифицированном каноне | исполнен | регистр, `fixed(P8-FIX)` |
|
||
| 12. Изоляция читающих чтений не запинена | исполнен, посадка сперва воспроизведена | `internal/pgstore/isolation_test.go` (`PD-333`) |
|
||
| 13. `CreditHeldBy`: оговорка не запинена | исполнен, посадка сперва воспроизведена | `internal/pgstore/credits_test.go` (`PD-334`) |
|
||
| `PD-255` (норма комментариев сменилась) | закрыт решением владельца 21.08 | регистр |
|
||
|
||
### Блокер свипа: почему лечение — не «поднять константу»
|
||
|
||
Диагноз пинга подтверждён построчно и **воспроизведён исполнением**. Живое свойство: проход 2 мин
|
||
против 60 с на прогон ⇒ двух медленных прогонов хватает, чтобы съесть проход целиком, после чего
|
||
`ctx.Err()` обрывает цикл и `UnsettledRuns` — единственный ретрай отложенного расчёта — не
|
||
вызывается ВООБЩЕ, для всей инсталляции, каждый проход. Построено:
|
||
|
||
1. **Проход разделён на фазы.** Реконсиляция получает половину остатка, расчёт — свой кусок
|
||
(`runs.phaseBudget`). Выбранный бюджет фазы больше не ошибка свипа: очередь просто ждёт тика.
|
||
2. **Отсрочка с растущим бэкоффом на ПОПЫТКЕ** (`run_attempts.reconcile_after`, миграция 00025).
|
||
Свип читает `RunsToReconcile`, а не `ListLiveRuns`; порядок «старейший первым» не тронут — он
|
||
верен для всего, что просто медленно, — а из ГОЛОВЫ заклиненный прогон убирает именно отсрочка.
|
||
Телеметрия (`Lag`) по-прежнему видит всё.
|
||
3. **Терминальное состояние после N неудач** (`runs.StalledAfter` = 5, ≈ четверть часа попыток):
|
||
прогон считается ЗАСТРЯВШИМ, гейдж `tm_platform_runs_stalled`, одна ERROR-строка на пересечении
|
||
порога — не на каждом тике.
|
||
4. **Ручка у ОПЕРАТОРА, которой не было вовсе:** `tmplatformctl runs [--stalled]` называет строку,
|
||
число неудач, следующую попытку, сумму холда и его возраст, юнит и последнюю ошибку;
|
||
`tmplatformctl run abandon --run <id> --reason <text> [--release-hold]` — терминальный вердикт.
|
||
Плюс `books --abandoned` и `book refresh` для второй половины (материализация).
|
||
|
||
**Границу «дальше не иду» называю явно:** автоматически решать про деньги прогона, о котором нельзя
|
||
спросить, этот пак НЕ стал. Правило реконсилятора везде — «я не смог спросить» ≠ «прогона нет»;
|
||
прогон, упавший N раз, это ровно прогон, о котором никто не смог спросить, и счётчик перед той же
|
||
ошибкой её не чинит. Автоматический ответ на этот вопрос и есть эскроу (строка 136, П-18), который
|
||
промт §4.6 из пака исключает. Что счётчик покупает — что человека ИЗВЕЩАЮТ; что покупает ручка —
|
||
что извещение чего-то стоит. Хвост записан строкой **П-20**, а не подразумевается.
|
||
|
||
Оба числа стали ручками: `TM_PLATFORM_SWEEP_BUDGET` и `TM_PLATFORM_RUN_BUDGET`. Второе —
|
||
отдельная находка пака: поле `runs.Config.RunBudget` было объявлено, задокументировано и **никем не
|
||
присваивалось** (`PD-331`), то есть ручка существовала в структуре и не делала ничего.
|
||
|
||
### Пункт 4: что построено и где проходит граница sqlc
|
||
|
||
**Построен гейт, который доходит туда, куда sqlc не дойдёт:** `TestEverySQLStatementParsesAgainstTheMigratedSchema`
|
||
сворачивает КАЖДЫЙ SQL пакета прямо из исходника (литералы, конкатенации, именованные константы,
|
||
плюс оба внутренних помощника, принимающих SQL параметром) и планирует его живым Postgres'ом
|
||
(`explain (generic_plan)`) против мигрированной схемы. **162 оператора, все планируются.**
|
||
Несворачиваемый SQL — ОШИБКА гейта, а не пропуск: единственное исключение выписано таблицей в самом
|
||
гейте (`store.go` `Ready`, где имя таблицы принадлежит goose). Тем же гейтом закрыт вопрос
|
||
фикс-листа «есть ли в read-модели запрос, которого не касается ни один тест»: теперь его касаются
|
||
все, на каждом прогоне батареи.
|
||
|
||
**Граница sqlc — мой выбор, аргумент:** склеек в пакете 25 из 147, фрагментов-констант 15, у
|
||
`lastRun` девять потребителей — read-модель для sqlc недостижима по построению, и это ровно та
|
||
часть, где рантайм-ошибки и случались. Свободных от склейки файлов целиком пять: `credits.go`(15) ·
|
||
`identity.go`(13) · `idempotency.go`(7) · `sessions.go`(5) · `observe.go`(1) = **41 запрос**.
|
||
Это единственный кусок, где инструмент силён; конверсия — отдельный пак (одиннадцать денежных
|
||
запросов идут внутри чужой транзакции, генерённый код коммитится, нужен пин версии и гейт
|
||
«сгенерённое актуально»). Взять его здесь на две-три ручки — ровно то, от чего предостерёг
|
||
фикс-лист: купить инструмент туда, где не болит. Заведено строкой **П-19**.
|
||
⚠ **Вопрос закрыт владельцем 22.08: sqlc берётся ОТДЕЛЬНОЙ СЕССИЕЙ, не внутри пака** — изменений
|
||
много и мешать их с содержательной работой нельзя. Граница остаётся названной: 41 запрос.
|
||
|
||
### Пункт 5, `PD-297`: отказ с причиной
|
||
|
||
Мерить нечем и не на чем. Сама строка регистра объявляет замер на здешнем стенде НЕпредставительным
|
||
(unix-сокет против управляемого PG по TCP — разница на два порядка), а корпусной книги нет: она
|
||
появляется на холодном прогоне движка, которым гейчена строка 202 (решение владельца 20.08). Правка
|
||
самого опасного на запись пути без замера — ровно то, что эта строка запрещает. Берётся вместе с
|
||
холодным прогоном.
|
||
|
||
### Пункт 9: расхождение с буквой пинга, названное вслух
|
||
|
||
Пинг просил свести условия В миграцию 00022. **Она РЕЛИЗНАЯ**, и гейт неизменности
|
||
(`migrations.sha256` + `TestReleasedMigrationsAreUnchanged`) её правку запрещает; правка гейта ради
|
||
этого была бы подгонкой под зелень, что промт и `CLAUDE.md` запрещают прямо. Поэтому 00022 остаётся
|
||
нормативным носителем вакуумированных цифр как есть, а условия дописаны в носители, которые править
|
||
законно. «96%» не удалено и получило своё условие: это ХОЛОДНЫЙ корпус акта 4 (636 мс против 24),
|
||
тогда как 80–83% — вакуумированный. Носители мерили разное и все честны.
|
||
|
||
### Строка 199(а): развилку НЕ решал — сдаю вопросом, как требует §4.2 промта
|
||
|
||
⚠ **ПОПРАВКА ВЛАДЕЛЬЦА 22.08, и она сносит посылку, на которой я строил разбор.** Я описывал трубу
|
||
как доставку ПЕР-ТЕРМНЫХ решений («approve/decline на термин»). **Такой модели нет и не было
|
||
с 16.08:** D39.144 ратифицировал ОДИН ОК над ВСЕМ банком — «пер-термная подпись = сотни кликов —
|
||
НЕ модель продукта», «в модели одного ОК сценария „какие 17 из 300 решены“ не существует».
|
||
Пер-термно существует не подпись, а **правка**: поправить/добавить термин до подписи, и — после
|
||
прочтения — поправить термин с пере-генерацией задетых глав (вторая половина гейчена строкой 192).
|
||
|
||
Труба от этого не отменяется, но её ГРУЗ другой: не «решения», а **правки терминов** (`src` → новый
|
||
`dst`, плюс добавленные) в `mined_delta`. Развилка ниже (кто объявляет ключи и кто создаёт файлы)
|
||
от этого не меняется ни в одном пункте — она про владение `book.yaml`, а не про груз.
|
||
|
||
⚠ **Модель, которую D39.144 отменил, была у зоны ПОСТРОЕНА — 22.08 СНЯТА по слову владельца**
|
||
(`PD-370`). Что ушло: маршрут `POST /books/{bookId}/bank/decisions`, хендлер и wire-типы, метод
|
||
интерфейса `Library`, `SubmitBankDecisions`, типы `BankDecision`/`BankReceipt`, `UnknownTermError`
|
||
и их пины. На месте write-пути — пометка для будущей сессии (`internal/httpapi/reading.go`, блок
|
||
«THE WRITE HALF OF THE BANK IS GONE»): что отменено, что ратификация ОСТАВИЛА пер-термным
|
||
(правку), и что под неё уже есть в схеме. ⚠ **Контрактная половина остаётся открытой** — канон
|
||
0.4.0 несёт путь, три схемы и глагол, и это зона контрактной сессии.
|
||
|
||
Посылку промта пере-проверил САМ, чтением кода движка, а не по докам. Подтверждается вся:
|
||
|
||
| Факт | Улика |
|
||
|---|---|
|
||
| движок читает `mined_delta`, только если путь объявлен | `backend/internal/pipeline/mining.go` `loadMinedDelta`: `if r.Book.MinedDelta == "" { return nil, nil }` — ни одного чтения |
|
||
| то же для `mined_rejects` | там же, `loadMinedRejects` |
|
||
| объявленный ключ с НЕсуществующим файлом валит загрузку конфига целиком | `backend/internal/config/book.go:199-207`: `bad("mined_delta %s is not readable: %v")` |
|
||
| ключей нет в шаблоне оператора | `backend/example/book.yaml` — ноль вхождений `mined_delta`/`mined_rejects` |
|
||
| платформа пишет `book.yaml` ОДИН раз и фиксированным набором из пяти ключей | `platform/internal/books/render.go` — `O_EXCL`, «writes the file once and never again»; ключи `book_id · title · source_lang · target_lang · source_file` |
|
||
| **новое, чего в промте не было:** декодер движка СТРОГИЙ — незнакомый ключ это жёсткая ошибка | `backend/internal/config/book.go:142-144`, `dec.KnownFields(true)` — то есть платформа не может «на всякий случай» дописать ключ, которого сборка движка не знает |
|
||
| относительные пути резолвятся от каталога КНИГИ | `backend/internal/config/book.go:148-162`, `resolve()` — значит один шаблон на все книги даёт пер-книжные пути без подстановки |
|
||
| форматы файлов | `mined_delta` = `seed.File` (`terms:` со схемой `internal/seed/seed.go:63-80`), грузится `membank.LoadGlossarySeed` и пере-штампуется `Source:"mined"`; `mined_rejects` = `rejects: [{src, note}]` (`mining.go` `minedRejectFile`), это ФИЛЬТР ПРЕДЛОЖЕНИЙ, в банк не входит |
|
||
| на стороне платформы решения write-only | `internal/pgstore/readmodel.go` `SubmitBankDecisions` пишет `action`/`dst`; ни один SELECT их не читает, воркер описан комментарием собственной миграции `00002_readmodel.sql:172-174` и не построен |
|
||
|
||
**Варианты с ценой (решает владелец, не я):**
|
||
|
||
- **A. Платформа объявляет два ключа при рождении книги и сразу создаёт два пустых файла.**
|
||
Работает без единой правки движка и без действий оператора; пустой файл законен (`terms:` пуст ⇒
|
||
ноль записей, `os.Stat` проходит). **Цена:** платформа начинает знать два ключа СХЕМЫ движка, а
|
||
форма Б ратифицирована ровно наоборот (D39.110 §2б: шаблон — деплой-артефакт оператора, платформа
|
||
подставляет только личность, языки и имя файла). Плюс строгий декодер: сборка движка, которая
|
||
этих ключей ещё/уже не знает, перестанет грузить КАЖДУЮ новую книгу.
|
||
- **B. Ключи несёт шаблон оператора.** Платформа схемы не узнаёт, пути пер-книжные достаются даром.
|
||
**Цена:** сам по себе вариант НЕ работает — движок отказывается грузить конфиг с объявленным, но
|
||
отсутствующим файлом, а создавать файлы всё равно некому, кроме платформы. То есть B требует
|
||
половины A или варианта D.
|
||
- **C. Двигается ратифицированная граница владения:** платформе разрешено писать в каталог книги эти
|
||
два файла (и только их), ключи несёт шаблон. **Цена:** правка ратификации D39.110; зато платформа
|
||
по-прежнему не знает схемы — она пишет файлы по путям, которые прочитала из уже существующего
|
||
`book.yaml`.
|
||
- **D. Движок терпит объявленный, но отсутствующий `mined_delta`/`mined_rejects` как пустой.**
|
||
Одна правка `backend/internal/config/book.go`, после чего работает чистый B: платформа пишет файлы
|
||
только когда решения есть, и ничего не обязано существовать заранее. **Цена:** ослабляется
|
||
намеренный гард — опечатка оператора в пути становится тихой. Смягчается тем, что послабление
|
||
только для двух ключей и с громким WARN. Это строка ЕДИНОГО бэклога (движок), не моя.
|
||
|
||
**Рекомендация зоны: C или D+B.** Обе оставляют платформу в неведении о схеме движка — а это и есть
|
||
то свойство, ради которого форма Б ратифицировалась. A решает быстрее всех и покупает это тем, что
|
||
платформа впервые начинает знать схему чужой стороны, причём под строгим декодером.
|
||
⚠ **Строить начну только после ратификации.** Границы, если разрешат, приняты как в §4.2: писать не
|
||
«перед resume», а только на том резюме, которое СНИМАЕТ стоп подписи (`l.VerifyBank &&
|
||
!l.BankReleased`); `--verify-bank` не трогать; пост-ридинговый цикл (строка 192) не проектировать.
|
||
`PD-277` в регистре относил эту работу к отложенной 192. ⚠ **22.08 он ПЕРЕ-ДИСПОЗИРОВАН** — не по
|
||
развилке, а по спилу глагола: остаток «честность `decline`» умер вместе с ним. Ниже — прежняя
|
||
формулировка обязательства, оставленная как след решения: правлю его только после ратификации, как
|
||
промт и велит.
|
||
|
||
### Как это проверялось
|
||
|
||
**Батарея:** `cd platform && make check` с ОБОИМИ гейтами (`TM_PLATFORM_TEST_DSN` на живом
|
||
PostgreSQL 18.4, `TM_PLATFORM_TEST_ENGINE_BIN` + `_BOOK_TEMPLATE`) — 18 пакетов, EXIT=0, скипов 0,
|
||
линтер «0 issues». Дифф имён тестов — исполнением: `grep -rho '^func Test[A-Za-z0-9_]*'` по дереву
|
||
против `git show HEAD:<файл>` по всем `_test.go`. ⚠ **Замер пере-снят при сдаче** и его исход
|
||
изменился: `comm -23` даёт ТРИ удалённых имени — все три описывали пер-термную подпись банка,
|
||
снятую 22.08 по слову владельца (`PD-370`, разбор в шапке), — а `comm -13` даёт 29 новых.
|
||
Прежняя формулировка «удалённых нет» была верна до спила и снята, а не оставлена рядом с новой.
|
||
|
||
**Посадки мутаций волны 1 — 20, поймано 20** (перечень ниже; счёт пере-снят при сдаче — прежняя
|
||
цифра «16» не сходилась с собственным списком в той же фразе). Каждая ставилась в код, пин обязан был упасть, код возвращался:
|
||
снятие `payload["revision"]` ✅ · снятие `payload["structure_version"]` ✅ · `ContractVersion` назад
|
||
на 0.3.0 ✅ · `inReadTx` без обеих опций ✅ · без изоляции ✅ · без read-only ✅ · `ListBooks` на
|
||
`inTx` ✅ · снятие `and book_id <> $2` ✅ · снятие пола самоописания манифеста ✅ · снятие пола «ноль
|
||
глав» ✅ · обе фазы свипа на ОДНОМ дедлайне ✅ · ранний возврат фазы из `Sweep` ✅ · свип читает
|
||
`ListLiveRuns` вместо списка «пора» ✅ · отсрочка только по ошибке ✅ · `DeferReadModelDebt` в
|
||
`now()` ✅ · долг никогда не списывается ✅ · новая граница наследует потраченный бюджет ✅ · колонка,
|
||
которой нет в схеме, в СКЛЕЕННОМ фрагменте (гейт SQL) ✅ · то же в локальной `const q` ✅ ·
|
||
несворачиваемый SQL ✅.
|
||
⚠ **Три посадки из этого списка сперва ПЕРЕЖИЛИ** мои же пины и вскрыли настоящие дефекты пака —
|
||
разбор в `PD-337`/`PD-338`/`PD-339`; пины переписаны, мутации пере-посажены.
|
||
|
||
**Воспроизведение ПЕРЕД пином — там, где промт этого требовал (пп. 2, 12, 13):** три мутации
|
||
посажены разом в HEAD-код и прогнана ПОЛНАЯ батарея — 18 пакетов, exit 0, скипов 0. То есть все три
|
||
дыры подтверждены исполнением, а не прочитаны в пинге.
|
||
|
||
**Живые пробы — настоящий `tmctl`, живой Postgres, дев-демон на 127.0.0.1:8098:**
|
||
- `GET /v0/capabilities` → `"contract_version": "0.4.0"` (пункт 1 на проводе);
|
||
- `tmplatformctl seed` — аккаунт, грант, книга через ЖИВОЙ `POST /v0/books`: интейк прошёл, движок
|
||
нарезал 3 главы, `GET /chapters` отдаёт 3, долг материализации погашен, `read_model_attempts = 0`.
|
||
**Это отрицательная проверка нового пола: законный манифест живого движка он НЕ отвергает;**
|
||
- **PD-162 воспроизведён живьём** — каталог книги унесён, прогон принят (202), спавн падает.
|
||
`tmplatformctl runs` печатает строку с числом неудач `1`, временем следующей попытки, суммой холда
|
||
`0.090000`, его возрастом, `(none)` вместо юнита и текстом ошибки движка. **До этого пака оператор
|
||
видел только растущий счётчик `sweep_unfinished_total` и не мог сделать ничего;**
|
||
- `run abandon` без флага: прогон `failed`/`service_error`, деньги не тронуты, книга РАЗМОРОЖЕНА —
|
||
следующий прогон принят (202). С флагом `--release-hold`: холд вернулся целиком;
|
||
- **деньги пере-считаны из сырого леджера двумя путями и сошлись до цента:** кэш
|
||
`account_balances.balance_micro_usd` = 5000000, `sum(amount_micro_usd)` по `credit_ledger` =
|
||
5000000, открытых холдов 0; строки леджера — `grant · hold · hold_release · hold · hold_release`;
|
||
- метрики на проводе: `tm_platform_runs_stalled 0`, `tm_platform_reading_surfaces_abandoned 0`;
|
||
- обе новые ручки конфигурации печатаются на буте с источником (`TM_PLATFORM_SWEEP_BUDGET` `2m0s`
|
||
`default`, `TM_PLATFORM_RUN_BUDGET` `1m0s` `default`) — норма PD-114.
|
||
|
||
**Гейты доков:** `python3 docs/scripts/counts.py --check` — exit 0, литералы сходятся;
|
||
`--lint` — exit 0.
|
||
⚠ **Раскрываю правку ЧУЖОГО документа:** пак убил два якоря в `PLATFORM_P8_REVIEW_SESSION_PROMPT.md`
|
||
(константы `sweepBudget`/`defaultRunBudget` переехали), и `--lint` был из-за этого КРАСНЫМ. Якоря
|
||
пере-нацелены на новые места, абзац оставлен как описание НАЙДЕННОГО, а не текущего кода. Правил
|
||
чужой промт сознательно и говорю об этом прямо: якоря умерли по моей вине, а красный гейт не
|
||
обходят. Оркестратор вправе откатить эту правку — содержательного текста она не трогает.
|
||
⚠ Первая моя проверка этого гейта дала EXIT=0 и была ЛОЖНОЙ: я мерил в момент, когда файла с
|
||
якорями в дереве не было, поэтому линт его не сканировал. Критик полноты был прав, я нет.
|
||
|
||
### Спил пер-термной подписи банка (слово владельца 22.08) и что осталось контракту
|
||
|
||
**Заказ:** модель отменена `D39.144` ещё 16.08, реализации нет — спилить в зоне, оставить пометку
|
||
будущим сессиям. Перед спилом — аудит воркфлоу: 5 линз (канон · код зоны · потребители ·
|
||
ратификация · будущая пометка) + адверсариальная проверка каждого рискованного вывода, **31 агент**.
|
||
|
||
**Снято из зоны:** маршрут `POST /books/{bookId}/bank/decisions` · хендлер `bankDecisions` и
|
||
wire-типы (`wireBankDecision`/`wireBankDecisions`/`wireBankReceipt`) · метод `SubmitBankDecisions`
|
||
из интерфейса `Library` и из стора · типы `BankDecision`/`BankReceipt` · `ErrUnknownTerm` /
|
||
`UnknownTermError` и их отображение в контрактный код · пины, которые их описывали.
|
||
|
||
**НЕ тронуто, и это проверено:** чтение банка (`GET /bank`, `BankTerm`, оси статуса и происхождения)
|
||
· вокабула ДВИЖКА `proposed|in_progress|approved` в сайдкар-инжесте — она чужая и не отменялась ·
|
||
сам стоп `awaiting_bank` и подпись как `resume` (D39.144 снимала ГЕЙТ, не СТОП) · таблица
|
||
`bank_decisions` и её констрейнты: миграции релизные, а колонки `term_id`/`dst` — ровно то, что
|
||
нужно будущей ПРАВКЕ термина. Данных под снос не было: на стенде ноль строк решений.
|
||
|
||
**Пометка на месте write-пути** (`internal/httpapi/reading.go`, «THE WRITE HALF OF THE BANK IS
|
||
GONE»): что именно отменено, что ратификация ОСТАВИЛА пер-термным (правку и добавление термина),
|
||
чего нет ни на одной поверхности, и что под это уже есть в схеме и в движке (`mined_delta`).
|
||
|
||
⚠ **Гейт размера поверхности сработал** — `len(contractSurface) < 15` поймал снятие маршрута. Пол
|
||
сдвинут 15 → 14 **с причиной в комментарии**: пол, опущенный под ратифицированное сокращение, — не
|
||
подгонка; опущенный без неё — подгонка. Два теста, стоявшие на снятой ручке как на «любом POST»,
|
||
пере-нацелены на живой маршрут, а не удалены.
|
||
|
||
#### Насколько страдает контракт (главный вопрос владельца)
|
||
|
||
Канон **не лжёт про модель** — `D39.144` в него внесена и записана в четырёх местах верно
|
||
(`openapi.yaml:443-448`, `:539-541`, `:707`, `:2498-2499`). Беда в другом: **рядом с новой моделью
|
||
в полном рабочем обмундировании стоит старая** — путь, три схемы, четыре счётчика, — и обе зовутся
|
||
словом «signing». Файл несёт ДВЕ модели подписи.
|
||
|
||
- **Прямая ложь — шесть мест** (четыре в каноне, два в компаньоне). Худшие: `:1830-1832`
|
||
(«статус подтянется на следующей пересборке» — не подтянется, решения до движка не доезжают, что
|
||
говорит сам же канон на `:1959-1963`) и компаньон `:672` («`POST /bank/decisions` НЕ ПОСТРОЕНО» —
|
||
было построено в P7 и снято сегодня).
|
||
- **Самопротиворечия — три.** Самое явное: `:443` «Signing the bank is ONE act over the whole of
|
||
it» против `:477` «Signing is not a row edit» — заголовок описания ручки называет подписью САМУ
|
||
РУЧКУ. Тридцать четыре строки между ними, и оба текста компилируются в сгенерированный клиент.
|
||
Плюс `:1918-1922` («строка может быть решена и НЕ подписана, потому что отказ — тоже решение») —
|
||
дословно отменённая модель — соседствует с `:1929-1932` («INFORMATIONAL, never a gate»).
|
||
- **Дыра, которую спил вскрывает и которую надо назвать вслух:** `approve` + непустой `dst` был
|
||
ЕДИНСТВЕННЫМ живым носителем того, что D39.144 оставила пер-термным — «поправить термин». А
|
||
«добавить свой термин» модель называет, и в контракте его **нет вообще**: на 17 путей канона
|
||
запись в банк была ровно одна. То есть после спила у собственной фразы канона `:539-541`
|
||
(«correct what they care to, then release it with ONE resume») **нет ручки**.
|
||
- **Счётчики вырождаются.** `pending_decisions` считается как «строки `proposed`, на которые НЕТ
|
||
записи в `bank_decisions`»; писателя больше нет ⇒ это «сколько строк предложено», а `complete`
|
||
истинно ровно тогда, когда read-out движка не несёт ни одной `proposed` (не константа: сайдкар
|
||
несёт и `in_progress`, и `approved`). Поля обязательные на проводе (`EventBank.required`), поэтому оставлены как есть:
|
||
менять их смысл — правка канона, а не зоны.
|
||
- **Объём и вес:** ~17 мест в `openapi.yaml` (~150 строк из 2592) + ~8 в компаньоне. Вес —
|
||
**ломающий минор 0.4.0 → 0.5.0**; мажора нет ни по политике («0.x ломает миноры весь бета-период»),
|
||
ни по факту. Цена фронту нулевая ДОПОЛНИТЕЛЬНО: его зеркало ратифицированно стоит на 0.2.3.
|
||
|
||
**Пока канон не поправлен, деплой обслуживает на одну ручку меньше, чем объявляет.** Это записано
|
||
строкой `PD-370` (зонная половина закрыта, контрактная открыта), и это осознанная цена спила, а не
|
||
недосмотр.
|
||
|
||
### Волна 2: воркфлоу-ревью и что оно нашло в САМОМ паке
|
||
|
||
Заказ владельца: после возврата ревьюеров волны 1 и починки их находок — воркфлоу пошире, 10–30
|
||
агентов, один Fable 5 на тяжелейший участок. Исполнено: **11 линз поиска + адверсариальный рефутер
|
||
на КАЖДУЮ находку + синтез, 47 агентов, 0 ошибок**. Fable 5 — один, на свипе и деньгах.
|
||
|
||
**36 находок, 21 пережила рефутера, 15 опровергнуто.** После склейки дублей (голодовку расчётной
|
||
фазы назвали пять линз, срез по байту — две) это **17 строк регистра**: `PD-353…PD-366` закрыты,
|
||
`PD-367`/`PD-368`/`PD-369` открыты — они не мои решения и переданы как вопросы.
|
||
|
||
⚠ **Итоговая строка самого воркфлоу («ни одна находка не пережила проверку») — БАГ МОЕГО скрипта
|
||
синтеза, а не вердикт.** Она противоречила сама себе (пустыми были и «выжившие», и «убитые»), и
|
||
таблица пере-собрана из `journal.jsonl` — по одной записи на агента. Говорю прямо: доверься я
|
||
сводке, пак уехал бы на приёмку с блокером внутри.
|
||
|
||
**Блокер, найденный ПЯТЬЮ независимыми линзами (включая Fable 5) и подтверждённый мной по коду
|
||
до их вердикта — `PD-353`.** Пак разделил проход на фазы, чтобы одна не могла съесть другую, и
|
||
оставил ВНУТРИ второй фазы то же самое голодание: `UnsettledRuns` упорядочен старейшим-первым,
|
||
каждый элемент стоит целый бюджет, а `settle` при нечитаемой трате возвращает nil — то есть клин
|
||
рапортует УСПЕХ, потратив проход. Одна недоступная книга замораживала холды ВСЕХ аккаунтов, каждый
|
||
проход. Это ровно та формулировка, которую шапка `Sweep` объявляла побеждённой.
|
||
|
||
**Ещё два, которые делали механизмы пака недостижимыми:**
|
||
- `PD-354` — счётчик неудач обнулялся ТИШИНОЙ: дорогой вопрос движку задаётся раз в `ResyncEvery`,
|
||
остальные проходы ничего не устанавливают, и `ClearRunDeferral` на них сбрасывал счёт. 1,0,1,0 —
|
||
`StalledAfter` недостижим, гейдж пуст, оператору не говорят никогда.
|
||
- `PD-355` — колонка SPENT в операторской таблице показывала пожизненную трату КНИГИ. Моя же
|
||
починка G8 волны 1 взяла не то число, на единственной таблице, заведённой ради решения о деньгах.
|
||
|
||
**Чему меня научили РЕФУТЕРЫ (15 опровержений, каждое куплено исполнением, а не рассуждением):**
|
||
- «Поднять `RunBudget` = объявить здоровый прогон застрявшим» — **опровергнуто замером**: поднятие
|
||
ручки не меняет вообще ничего, а строгая проверка `RunBudget < SweepBudget/2` отвергла бы сами
|
||
шиппящиеся дефолты. Я уже держал эту «починку» в плане и не построил её. Остаток — `PD-368`.
|
||
- «Интейк обязан применить `Whole()`» — **опровергнуто контрактом и батареей**: вся батарея интейка
|
||
ездит на манифестах без списка глав, контракт интейка — только счётчики. Механика находки верна
|
||
(`PD-367` открыт), лекарство — нет.
|
||
- «Пропуск тела помощника в гейте SQL = дыра» — **опровергнуто исполнением**: мутация в теле `bump`
|
||
краснит десятки поведенческих тестов; спрятать там непроверяемый оператор нельзя.
|
||
- «Стойл-детектор слеп к форме PD-162» (находка Fable 5) — опровергнуто.
|
||
|
||
**Две линзы разошлись во ВЕСЕ одной находки** (`PD-358`, срез по байту в операторской таблице):
|
||
одна назвала микрополировкой вне рамки, вторая довела цепочку до многобайтового stderr движка.
|
||
Починил как дешёвое и бесспорное, расхождение раскрыл в строке.
|
||
|
||
### Самопроверка (артефакт §5: имена агентов и по одной команде или цитате из каждого)
|
||
|
||
Исходов три, «не проверено» ≠ «опровергнуто». Каждому агенту выдан СВОЙ каталог песочницы явным
|
||
путём, мандат читающий, state-changing git запрещён перечислением (D39.113). В рабочее дерево не
|
||
писал никто: оба ревьюера волны 1 ответили на прямой вопрос уликами.
|
||
|
||
**Волна 1 — три изолированных ревьюера по финальному диффу (author≠reviewer):**
|
||
|
||
| Агент | Ось | Цитата или команда ИЗ ЕГО отчёта | Находок | Исход |
|
||
|---|---|---|---|---|
|
||
| `agent-afec473415b228cdf` | контракт-конформность против канона 0.4.0 + свои посадки | «в песочнице создан симлинк `<scratch>/docs -> …/docs` — новый гейт `internal/gates/contract_test.go` тянется за канон ЗА пределы модуля, и без него копия дерева его не находит» | 5 + 3 пережившие мутации | все 8 починены |
|
||
| `agent-a394edded388676bc` | шов с движком и деньги, свой инвентарь каналов | «`repo*/platform/cmd/rev2probe\|rev2ctl\|rev2seam\|rev2ceiling\|rev2ids/main.go` и `repo*/platform/internal/runs/rev2_starve_test.go` — мои пробы (только в копиях)» | 8 + 3 пережившие мутации | все 11 починены; G1 — БЛОКЕР в моём коде |
|
||
| `agent-a1e4407d89e52128d` | полнота сдачи: пункт заказа → носитель в дереве | «Snapshot taken at 2026-08-22 00:49:24. ⚠ The tree was moving while I read it: `platform/BACKLOG.md` gained rows П-19/П-20 at 00:48, mid-review» | 6 | 5 починены, 1 (К-4) вскрыла МОЮ ошибку измерения |
|
||
|
||
Две разведки (read-only, не ревью): `agent-acf370d564663617e` — инвентарь SQL для границы sqlc;
|
||
`agent-a1cb3d1a5c59f2f2e` — инвентарь каналов движка.
|
||
|
||
**Волна 2 — воркфлоу `wf_f8d1f3e6-85a`, 11 линз (одна на Fable 5) + рефутер на каждую находку:**
|
||
|
||
| Линза (агент) | Предмет | Цитата или команда ИЗ ЕГО отчёта | Находок | Пережило |
|
||
|---|---|---|---|---|
|
||
| `a2fdcd13144058b57` **Fable 5** | свип и деньги — тяжелейший участок | «Стойл-детектор слеп к клину „юнит исчез, workdir недоступен“ (форма PD-162): реконсиляция каждый проход рапортует успех» | 3 | 2 |
|
||
| `ae8f856fe08ee9e61` | фазы и отсрочка свипа | «Счётчик отсрочек не может дорасти до StalledAfter для того самого клина, ради которого написан» | 4 | 3 |
|
||
| `a3c4dc82a7d62f8a6` | архитектурный стиль, велосипеды | «Фаза расчёта денег осталась без отсрочки: head-of-line starvation, от которой пак и построен, на списке `UnsettledRuns` не починена» | 3 | 1 |
|
||
| `a62786a00eb5bbae4` | каноны Go и каноны зоны | «RunBudget молча обрезан половиной SweepBudget» (опровергнуто) + голодовка расчётной фазы | 4 | 2 |
|
||
| `af8620a80b7e12b06` | материализация и пол манифеста | «Провалившийся экспорт на смене кадра затирает весь оплаченный текст книги» | 3 | 3 |
|
||
| `ada2420ac7c576856` | CLI, метрики, конфиг | «SPENT в операторской таблице — пожизненный счётчик КНИГИ, а не траты этой попытки» | 3 | 3 |
|
||
| `ac92e0a8acba148ec` | сам гейт SQL: даёт ли ложную зелень | «`unresolvable` ключёвана функцией, а не сайтом: второй несворачиваемый запрос в той же функции молча проверяется текстом первого» | 2 | 1 |
|
||
| `a21f43459f3f12f7e` | комментарии: вода против несущего | «perf_test.go копирует „нормативные“ цифры из миграции 00022 и в той же фразе утверждает, что не копирует» | 4 | 1 |
|
||
| `a8faf63f1dde50903` | скоуп: сверка построенного против БУКВЫ промта | «Пункт 9: новое условие замера в perf_test.go ложно для бенчмарка этого же файла» | 4 | 2 |
|
||
| `a4dad647c0eb8310f` | регрессии: что пак сломал в уже работавшем | «Отмена контекста сообщается как дедлайн: остановка демона посреди прохода поднимает счётчик голодания» | 3 | 2 |
|
||
| `a178aca863af19915` | контракт-конформность построенного против канона 0.4.0 | «Пункт 1: подъём константы оставил в коде утверждение, что деплой объявляет 0.3.0» (опровергнуто) | 3 | 1 |
|
||
|
||
Рефутеры (по одному на находку, 36 штук) считаются наравне с находками — три примера того, чем
|
||
куплены их вердикты:
|
||
- `ad12d9bcca05b883a`: «`WITH the index n_tup_upd=3942 n_tup_hot_upd=3909 (HOT 99.2%) idx size=8192`
|
||
против `WITHOUT the index n_tup_upd=1994 n_tup_hot_upd=1984 (HOT 99.5%)`» — из этого замера
|
||
выросла `PD-364` (индекс удалён);
|
||
- `a337318688be20d30`: «`finished_at = now()` → `books.go:313: column "finished_at" of relation
|
||
"books" does not exist (42703)` … на всех 5 сайтах»;
|
||
- `a7fadd3c541e9a87d`: «пять проходов подряд дают „the sweep settled 2 of 3 finished runs: context
|
||
deadline exceeded“, третий, чистый прогон не достигается никогда, reserved держится на $0.90» —
|
||
это подтверждение блокера `PD-353` исполнением.
|
||
|
||
**Посадки мутаций волны 2 — 6, поймано 6:** фаза расчёта не отсрочивает ✅ · `UnsettledRuns`
|
||
игнорирует отсрочку ✅ · тишина снова считается здоровьем ✅ · `RunBudget` снова не присваивается ✅ ·
|
||
срез по байту в операторской таблице ✅ · управляющие символы не вычищаются ✅.
|
||
⚠ Две первые попытки посадить последние две дали НЕ падение теста, а ошибку сборки (осиротевший
|
||
импорт) — это слабее, и они пере-посажены компилирующимися.
|
||
|
||
### Релей §4.7: не приходил
|
||
|
||
Аддендум пака P8-REVIEW должен был доехать **только релеем оркестратора с эхо-подтверждением**.
|
||
За сессию релея не поступало: ни находок по четырём осям, ни эхо. Отчёт читающего пака я не читал
|
||
и не гадал по нему — промт это прямо запрещает, состав пака определяет оркестратор.
|
||
|
||
Единственное касание того пака — техническое и раскрыто выше: два его якоря умерли от МОИХ правок
|
||
кода, `--lint` покраснел, якоря пере-нацелены (`docs/PLATFORM_P8_REVIEW_SESSION_PROMPT.md:62`,
|
||
плюс третье пере-нацеливание 22.08 после спила). Содержательного текста чужого промта не трогал.
|
||
|
||
### Obstacle: что не удалось и что не проверено
|
||
|
||
- **Сквозного живого перевода через платформу не было** и в этом паке быть не могло: строка 202
|
||
гейчена холодным прогоном движка (решение владельца 20.08). Всё после «прогон стартовал и
|
||
остановился» доказано батареей против живого Postgres и настоящего `tmctl`, а не переводом.
|
||
- **Труба 199(а) не построена и форматы сверены ЧТЕНИЕМ**, а не записью файла: `mined_delta` и
|
||
`mined_rejects` прочитаны в коде движка (`membank.LoadGlossarySeed`, `mining.go`), но ни одного
|
||
файла платформа не писала — писать было бы строительством нератифицированной развилки.
|
||
- **Замер `PD-297` не снят.** Отказ осознанный: путь самый опасный на запись, править его надо с
|
||
замером на корпусной книге до и после, а корпусной книги в паке не было.
|
||
- **Финальный дифф ПОСМОТРЕН** независимым ревьюером (author≠reviewer) уже после спила — он и
|
||
закрыл эту строку obstacle. Три его major были настоящими дырами в МОЁМ коде, и все три —
|
||
отсутствие пина, а не ошибка логики: пол `ErrTextUnknownForANewCut` (охраняет единственную
|
||
запись, способную стереть оплаченный текст) · «стоп перевешивает отсрочку» в `RunsToReconcile` ·
|
||
«пересборка банка не трогает `bank_decisions`» (свойство, на которое опирается пометка, ради
|
||
которой спил и делался). Каждая закрыта пином, каждый пин проверен посадкой мутации. Плюс семь
|
||
минорных: комментарии `SaveBank` и `bankCountsTx` описывали отменённую модель как живую,
|
||
пометка врала про `action NOT NULL`, снятие NUL не было запинено. Не найдено дефектов там, где
|
||
он искал отдельно: пол поверхности 15→14 равен реальному размеру таблицы, пере-нацеленные тесты
|
||
проверяют то же свойство, лишнего не срезано.
|
||
- **Число скипов батареи** снято рецептом `make check`, который пишет `.check.log` в дерево; при
|
||
параллельных прогонах агентов лог обрезался. Отдельная сверка двух пакетов с условными скипами
|
||
(`internal/runner`, `internal/gates`) дала `--- SKIP` = 0.
|
||
- **Контрактная половина `PD-370` открыта**: пока канон не поправлен, деплой обслуживает на одну
|
||
ручку меньше, чем объявляет. Это цена спила, названная заранее, а не обнаруженная при приёмке.
|
||
|
||
### Инвентарь каналов движка (артефакт §4.4 — собран ЧТЕНИЕМ кода движка, не по нашим докам)
|
||
|
||
Пак коснулся канала движка (пункт 10: `ingest.Manifest.Whole()` построен как зеркало движкового
|
||
правила), поэтому инвентарь сдаётся таблицей, а не словами «я прочитал код движка».
|
||
|
||
| Канал | Писатель в движке | Атомарность | Читатель/писатель платформы | Согласовано? |
|
||
|---|---|---|---|---|
|
||
| `<project_db>.manifest.json` | `pipeline/manifest.go:379` `writeFileAtomic` | **атомарно** (temp+`Sync`+rename, `pipeline/artifact.go:24-59`) | `internal/runner/engine.go` (`tmctl manifest`) → `ingest.DecodeManifest` → `internal/books/parse.go`, `internal/readmodel` | ✅ **и стало строже**: `Manifest.Whole()` теперь зеркалит `BookManifest.selfConsistent` (`pipeline/manifest.go:460-483`) поле в поле по тем правилам, которые платформа вообще читает (главы, юниты, нумерация). Верхняя граница чанков движка у нас counterpart'а не имеет намеренно — платформа счётчиков чанков не берёт |
|
||
| `<project_db>.bank.json` | `pipeline/bankexport.go:154` `writeFileAtomic` | **атомарно** | `internal/runner/artifacts.go:37` → `ingest.DecodeBank` → `pgstore.SaveBank` | ✅ |
|
||
| `<project_db>.bank-stop.json` | `pipeline/mining.go:393` `writeFileAtomic` | **атомарно** | **читателя НЕТ** (грепом по зоне — ноль вхождений) | ⚠ канал существует и не потребляется; полная таблица подписи платформе сегодня не нужна, она проецирует банк |
|
||
| `<project_db>.bank-stop.txt` | `pipeline/mining.go:195` `os.WriteFile` | **НЕ атомарно** (усечение первым делом) | читателя нет | ✅ и это важно: брать его на живом прогоне нельзя — исправление пинга №15 оркестратором подтверждается кодом |
|
||
| `<project_db>.mined-signature.yaml` | `pipeline/mining.go:188` `os.WriteFile` | **НЕ атомарно** | читателя нет | ✅ то же |
|
||
| `<project_db>.auto-bank.yaml` | `pipeline/mining.go:741` `os.WriteFile` | **НЕ атомарно** | читателя нет | ✅ то же |
|
||
| `mined_delta` (путь из `book.yaml`) | **писателя в движке НЕТ** — только читатель `pipeline/mining.go` `loadMinedDelta`; формат `seed.File` (`internal/seed/seed.go:63-80`), грузится `membank.LoadGlossarySeed`, `Source` пере-штампуется на `"mined"` | — | **писателя НЕТ и у платформы** | ❌ **разрыв — это и есть строка 199(а)** |
|
||
| `mined_rejects` | читатель `pipeline/mining.go` `loadMinedRejects`; формат `rejects: [{src, note}]`; ФИЛЬТР ПРЕДЛОЖЕНИЙ, в банк не входит | — | писателя нет | ❌ тот же разрыв |
|
||
| `events.jsonl` (NDJSON эмиттера) | движок, StreamVersion 1.1 | append-only | `internal/ingest/tail.go` + `pgstore.RunSink` | ✅ пак не трогал |
|
||
| exit-коды `tmctl` | контракт движка | — | `ingest.OutcomeOf`, `internal/runs/reconcile.go` `outcome` | ✅ пак не трогал |
|
||
| `tmctl status --json` | движок | — | `internal/runner/engine.go` → `ingest.StatusReport`; расчёт денег | ✅ пак не менял форму; изменил лишь то, СКОЛЬКО раз его зовут при неудаче (бэкофф) |
|
||
| `book.yaml` | оператор (шаблон) + **платформа один раз** (`internal/books/render.go`, `O_EXCL`) | — | платформа ПИШЕТ пять фиксированных ключей | ⚠ **декодер движка СТРОГИЙ** (`config/book.go:142-144`, `KnownFields(true)`): незнакомый ключ = жёсткая ошибка. Значит любое расширение набора ключей платформой — ратификация, а не правка. Прямо относится к развилке 199(а) выше |
|
||
|