Second cut pass in the platform zone: restore what the review found lost, and take the sediment the first pass left behind
This commit is contained in:
parent
1d84cff75e
commit
21de1779ba
9 changed files with 270 additions and 347 deletions
|
|
@ -2,31 +2,31 @@
|
|||
|
||||
> Ведёт зона `platform/` (решение владельца 02.08, D39.84: фронт и платформа держат СВОИ бэклоги; единый бэклог `docs/PROGRESS.md` остаётся трекером движка/полигона/доков и фронт/платформа-строк не принимает). Нормы те же: ID стабилен навсегда, каждая петля получает диспозицию. Запросы к ДВИЖКУ сюда не пишутся — они заходят строками единого бэклога через оркестратора (пример: строки 99–102). Засеян оркестратором при лендинге D39.84 — дальше правит платформа-сессия.
|
||||
|
||||
> **Диспозиции после P1 (05.08)** — в журнале зоны, раздел «Диспозиции бэклога зоны»
|
||||
> (`docs/platform-PROGRESS.md`). Коротко: П-6 и П-8 ЗАКРЫТЫ, П-7 закрыт по схеме и операциям
|
||||
> **Диспозиции после P1 (05.08)** — раздел «Диспозиции бэклога зоны» архив-слайса зонного журнала
|
||||
> (`docs/archive/platform-PROGRESS-P0-P3.md`). Коротко: П-6 и П-8 ЗАКРЫТЫ, П-7 закрыт по схеме и операциям
|
||||
> (постановка холда воркером — часть П-1), П-4 отменён и поглощён П-7, П-5 переопределён под
|
||||
> кредитную модель и ждёт правки спеки, П-1 продолжена, П-2/П-3 не трогали.
|
||||
> Дублировать их здесь не стали — у строки один источник истины.
|
||||
|
||||
| ID | Хвост | Вес | Источник |
|
||||
|---|---|---|---|
|
||||
| П-1 | **HTTP/SSE-слой и сервисная обвязка** (экс-строка 96 единого бэклога) — ⚠ **ЗАКРЫТА, живого хвоста нет (диспозиция 22.08, аудит доков).** Аутентификация, сессии и CSRF — P1; тейлер `events.jsonl` с курсором и карантином проекции, реконсилятор и пять ручек `/v0` — P4/P5; потребительская половина шва (коды выхода, фолд присваиванием, потолки) — P6. Последний остаток, читающая поверхность, был вынесен отдельной строкой **П-17** и исполнен паком P7 (D39.153), после чего «часть исполнена» перестало что-либо значить. Форма потока и аутентификации ратифицирована D39.84/D39.85 (`docs/research/23-engine-platform-seam.md`); порядок деплоя против schema-mismatch движка — `deploy/README.md`, §«Апгрейд ДВИЖКА» | закрыта | D39.81, D39.84, D39.85, STACK_DECISIONS §5 |
|
||||
| П-1 | **HTTP/SSE-слой и сервисная обвязка** (экс-строка 96 единого бэклога) — ⚠ **ЗАКРЫТА, живого хвоста нет (диспозиция 22.08, аудит доков).** Что именно построено — `platform/README.md` §«Что здесь будет»; последний остаток, читающая поверхность, вынесен строкой **П-17** и исполнен P7 (D39.153). Форма потока и аутентификации ратифицирована D39.84/D39.85 (`docs/research/23-engine-platform-seam.md`); порядок деплоя против schema-mismatch движка — `deploy/README.md`, §«Апгрейд ДВИЖКА» | закрыта | D39.81, D39.84, D39.85, STACK_DECISIONS §5 |
|
||||
| П-2 | **Глобальный брокер рейт-лимитов провайдеров** (экс-строка 97): гарды движка per-процесс (`pipeline/ratelimit.go:11`, mistral ~48% отказов под параллелизмом), а лимит провайдера — на ВЕСЬ аккаунт: N прогонов = N независимых гардов против общего лимита; воркер отпрашивается у платформы перед вызовом | ДО второго параллельного пользователя | D39.81, D39.84 |
|
||||
| П-3 | **Очередь и конкурентность по книге** — ⚠ **ИСПОЛНЕНО P4 в той форме, которая была нужна:** River на том же Postgres, `MaxAttempts: 1` (повтор не доделывает работу, а спавнит второй движок — работу восстанавливает реконсилятор), одновременность по книге закрыта не лизами, а частичным уникальным индексом `runs_one_live_per_book` в БД: двух живых прогонов на книге не бывает, потому что движок держит эксклюзивный лок на файле проекта. **ОСТАЮТСЯ** ровно те части, которые нужны только со ВТОРЫМ хостом: пиннинг книги к хосту и лизы `book_leases` с heartbeat — сегодня хост один, и лиза без второго хоста охраняет от самой себя | до второго хоста | STACK_DECISIONS §5 и §19, D39.84 |
|
||||
| П-4 | **Учёт токенов/денег per-user + бюджет-гейт ДО старта задачи** (сырьё уже считает движок: `request_log` + `internal/ledger`; помнить: леджер = нижняя граница — строка 78 единого) | Ф3-пак платформы | platform/README, D39.84 |
|
||||
| П-6 | **OAuth-вход популярных провайдеров (Google первым) + модель аккаунта** (направление владельца 04.08): authorization code + PKCE, валидация ID-токена, связывание аккаунта по ПОДТВЕРЖДЁННОЙ почте; собственная серверная сессия сохраняется (D39.84) — OAuth даёт только СОБЫТИЕ входа. Форма и библиотеки — по ресёрч-пакету направления (ратифицирует оркестратор); писатель куки с `__Host`-атрибутами и dev-профиль — здесь же (PD-8) | P1, вместе с П-1 | владелец 04.08 |
|
||||
| П-7 | **Кредитный баланс и фри-тир** (решения владельца 05.08; ПРОДАЖИ НЕТ — бета на предоплаченных ключах владельца): append-only леджер в целых микро-долларах, грант фри-тира из админки (дефолт $5, настраиваемый), холд ДО спавна прогона + пер-книжный потолок движку (жёсткий стоп исполняет движок), расчёт на границе попытки. ⚠ «дефолт $5» ПРОТУХ: слово владельца 16.08 (D39.138 п.2л, PD-104) — грант по умолчанию НОЛЬ, начисление руками; возврат $5 идёт вместе с суточным агрегатным потолком. ⚠ Окон с обнулением НЕТ — баланс, а не подписка: черновик `usage_windows` в подписочной форме заменяется. Платёжный провайдер не проектируется до решения продавать. Разбор — `docs/PLATFORM_DIRECTION.md` §2 | P1+, после П-6 | владелец 04–05.08 |
|
||||
| П-8 | **Минимальная админ-поверхность**: начислить/списать кредиты аккаунту (одна запись леджера), посмотреть баланс и состояние прогонов. Защищённая ручка либо CLI — форму предлагает сессия. Без неё фри-тир неуправляем | вместе с П-7 | владелец 05.08 |
|
||||
| П-5 | **API лимитов/использования + оповещение стопа по потолку** (решение владельца 04.08, D39.100/ПТ-35, механизм «как Claude Code»): страница лимитов в настройках читает СТАТУС использования (не суммы — деньги на провод не идут, D39.84); стоп по потолку → статус `paused` + оповещение «перевод остановлен: лимиты исчерпаны»; сырьё у движка есть (`request_log`/`ledger`, потолки конфига), форму API предлагает P0 | Ф3, вместе с П-1 | D39.100, ПТ-35, контракт К-8 |
|
||||
| П-9 | **Загрузка книги `POST /books` (multipart)** — **ИСПОЛНЕНО P5 (11.08):** ручка построена потоково (`r.MultipartReader`, пер-маршрутный потолок тела, свой дедлайн чтения), хранилище — каталог книги под `TM_PLATFORM_BOOKS_DIR`, статусы `uploading → parsing → not_started \| rejected` получили писателей, разбор зовёт `tmctl manifest`, PD-72 закрыт своим тестом. Развилка «кто пишет стартовый `book.yaml` при интейке» (D39.110 §2b, шов `books.ErrNotProvisioned`) **РЕШЕНА D39.130:** бета = форма Б (стройка — П-14); движковая форма В (`tmctl init`) = строка 170 единого | Ф3, следующий пак | сессия P4 (границей промта D39.119 §5а), исполнено P5 |
|
||||
| П-3 | **Очередь и конкурентность по книге** — ⚠ **ИСПОЛНЕНО P4 в той форме, которая была нужна:** River на том же Postgres, `MaxAttempts: 1` (довод — `STACK_DECISIONS` §19), одновременность по книге закрыта не лизами, а частичным уникальным индексом `runs_one_live_per_book` в БД. **ОСТАЮТСЯ** ровно те части, которые нужны только со ВТОРЫМ хостом: пиннинг книги к хосту и лизы `book_leases` с heartbeat — сегодня хост один, и лиза без второго хоста охраняет от самой себя | до второго хоста | STACK_DECISIONS §5 и §19, D39.84 |
|
||||
| П-4 | **Учёт токенов/денег per-user + бюджет-гейт ДО старта задачи** — ⚠ **ОТМЕНЁН и поглощён П-7** (диспозиция P1, шапка выше). ⚠ Оговорка жива и после поглощения: леджер = НИЖНЯЯ граница траты (строка 78 единого) | отменён | platform/README, D39.84 |
|
||||
| П-6 | **OAuth-вход популярных провайдеров (Google первым) + модель аккаунта** (направление владельца 04.08) — ⚠ **ЗАКРЫТ P1:** authorization code + PKCE, собственная серверная сессия сохраняется (D39.84 — OAuth даёт только СОБЫТИЕ входа), ключ личности `(provider, subject)`; код — `internal/login/`, состав и живой остаток — диспозиция из шапки. Писатель куки с `__Host`-атрибутами и dev-профиль — PD-8 | P1, вместе с П-1 | владелец 04.08 |
|
||||
| П-7 | **Кредитный баланс и фри-тир** (решения владельца 05.08; ПРОДАЖИ НЕТ — бета на предоплаченных ключах владельца): append-only леджер в целых микро-долларах, грант фри-тира из админки (**дефолт НОЛЬ**, начисление руками — `tmplatformctl grant`), холд ДО спавна прогона + пер-книжный потолок движку (жёсткий стоп исполняет движок), расчёт на границе попытки. ⚠ Прежний «дефолт $5» ОТМЕНЁН словом владельца 16.08 (D39.138 п.2л, PD-104); возврат $5 идёт вместе с суточным агрегатным потолком. ⚠ Окон с обнулением НЕТ — баланс, а не подписка: черновик `usage_windows` в подписочной форме заменяется. Платёжный провайдер не проектируется до решения продавать. Разбор — `docs/PLATFORM_DIRECTION.md` §2 | P1+, после П-6 | владелец 04–05.08 |
|
||||
| П-8 | **Минимальная админ-поверхность**: начислить/списать кредиты аккаунту (одна запись леджера), посмотреть баланс и состояние прогонов — ⚠ **ЗАКРЫТ P1 формой CLI:** `tmplatformctl grant/adjust/balance/logins/revoke` (`cmd/tmplatformctl/`) | вместе с П-7 | владелец 05.08 |
|
||||
| П-5 | **API лимитов/использования + оповещение стопа по потолку** (решение владельца 04.08, D39.100/ПТ-35, механизм «как Claude Code»): страница лимитов в настройках читает СТАТУС использования (не суммы — деньги на провод не идут, D39.84); стоп по потолку → статус `paused` + оповещение «перевод остановлен: лимиты исчерпаны». ⚠ **ПЕРЕОПРЕДЕЛЁН под кредитную модель (диспозиция P1, шапка выше):** окон и `resets_at` нет, форма ответа там же предложена, **ручка НЕ построена — ждёт правки спеки** | Ф3, вместе с П-1 | D39.100, ПТ-35, контракт К-8 |
|
||||
| П-9 | **Загрузка книги `POST /books` (multipart)** — **ИСПОЛНЕНО P5 (11.08)**: состав в `platform/README.md` §«Что здесь будет»; PD-72 закрыт своим тестом. Развилка «кто пишет стартовый `book.yaml` при интейке» (D39.110 §2b, шов `books.ErrNotProvisioned`) **РЕШЕНА D39.130:** бета = форма Б (стройка — П-14); движковая форма В (`tmctl init`) = строка 170 единого | Ф3, следующий пак | сессия P4 (границей промта D39.119 §5а), исполнено P5 |
|
||||
| П-10 | **Честная оценка «$/глава» от ДВИЖКА.** Сейчас ставка — константа платформы ($0.03, провенанс exp08 v2 через D30.4, `STACK_DECISIONS` §20), и это осознанная бета-мера: движковой поверхности оценки не существует, а выдумывать её запрещено. Ставка решает только ДЛИНУ шкалы (деньги защищены холдом и потолком движка), но на книге, которая заметно дороже или дешевле средней, шкала врёт пользователю о том, сколько глав он покупает. Нужна оценка от движка по конкретной книге — запрос уходит строкой ЕДИНОГО бэклога через оркестратора, не сюда | когда-нибудь (до первого платящего) | сессия P4 |
|
||||
| П-11 | **Наблюдаемость раннера** — **ИСПОЛНЕНО P5 (11.08):** `prometheus/client_golang` v1.24.1 на отдельном слушателе `TM_PLATFORM_METRICS_ADDR`; глубина очереди · возраст самого старого открытого холда · карантины · отставание тейлера · книги в интейке · длительность свипа и счётчик недоведённых проходов · запросы и задержки по паттерну маршрута. Ось наблюдаемости получила внешний эталон (практики именования Prometheus + золотые сигналы, `ENGINEERING_STANDARDS` §2) — половина PD-115, вторая половина строки остаётся открытой | Ф3 | сессия P4, исполнено P5 |
|
||||
| П-12 | **Квота интейка и ретеншен отклонённых книг.** `POST /books` даёт аутентифицированному аккаунту писать на диск оператора: один аплоад ограничен (64 МиБ), число аплоадов — ничем. Отклонённая по вине источника книга каталог теряет, отклонённая по вине деплоя — сохраняет намеренно, и не чистит их никто. Строка регистра — PD-175 ⚠ **ПРОДУКТОВАЯ ПОЛОВИНА СНЯТА ЦЕЛИКОМ — D39.176 п.1 (слово владельца 30.08), диспозиция записана паком P12 31.08:** продуктовых КВОТ НЕТ и не будет, фри-тир-лимиты не проектируются, живём на покупке API и зачислениях из админки. То есть «сколько книг входит во фри-тир» — больше НЕ развилка и НЕ ждёт владельца; вопроса нет. Остаётся ИНЖЕНЕРНАЯ гигиена, решаемая зоной без чьего-либо слова: ретеншен отклонённых (`rejected`) книг · свип каталогов-сирот · потолок диска. Гейт у неё один и он не продуктовый — открытая регистрация, которой в закрытой бете нет. | инженерная гигиена, до открытой регистрации | сессия P5; продуктовая половина снята D39.176 |
|
||||
| П-12 | **Квота интейка и ретеншен отклонённых книг.** `POST /books` даёт аутентифицированному аккаунту писать на диск оператора: один аплоад ограничен (64 МиБ), число аплоадов — ничем. Отклонённая по вине источника книга каталог теряет, отклонённая по вине деплоя — сохраняет намеренно, и не чистит их никто. Строка регистра — PD-175 ⚠ **ПРОДУКТОВАЯ ПОЛОВИНА СНЯТА ЦЕЛИКОМ — D39.176 п.1 (слово владельца 30.08):** продуктовых КВОТ НЕТ и не будет, фри-тир-лимиты не проектируются, живём на покупке API и зачислениях из админки; «сколько книг входит во фри-тир» — больше НЕ развилка и НЕ ждёт владельца. Остаётся ИНЖЕНЕРНАЯ гигиена, решаемая зоной без чьего-либо слова: ретеншен отклонённых (`rejected`) книг · свип каталогов-сирот · потолок диска. Гейт у неё один и он не продуктовый — открытая регистрация, которой в закрытой бете нет. | инженерная гигиена, до открытой регистрации | сессия P5; продуктовая половина снята D39.176 |
|
||||
| П-13 | **Оценка размера книги в символах — у ДВИЖКА.** `character_count` контракта платформа считает потоково на приёме (байты, не являющиеся продолжением UTF-8), что точно для UTF-8 и приблизительно для GB18030/UTF-16, которые движок принимает и декодирует сам. Манифест несёт `source_bytes` и `encoding`, но не число символов. Запрос уходит строкой ЕДИНОГО бэклога через оркестратора; строка регистра — PD-177 **→ строка 171 единого заведена 14.08** | когда-нибудь | сессия P5 |
|
||||
| П-15 | **ИСПОЛНЕНО P6 (14.08).** Платформенная половина шва эмиттера (движковая залендена D39.131, `9cfe080`) | ИСПОЛНЕНО P6: коды выхода движка 4/5/10–19 через `ingest.OutcomeOf`, потолок = `paused` двумя каналами (PD-113 закрыт), интейк судит по коду (PD-196 закрыт), exit 5 без намерения = прерывание и перезапуск (PD-152 закрыт); фолд `unit_done` ПРИСВАИВАНИЕМ по тройке (chapter, unit, wave) через `unit_resolutions` (миграция 00015; ратифицировано D39.131 п.2г, at-least-once); StreamVersion 1.1, `Ceiling.Scope` (book\|day), внутренняя причина `daily_ceiling`, резюм дневного потолка = 409 (PD-157 половина); dev-супервизор читает ту же таблицу; foreign-hello adoption воспроизведён и закрыт (PD-200); порядок деплоя — `deploy/README.md` + `tmplatformctl books --migratable`. ⚠ Граница присваивания (PD-219): оно самолечит ПОВТОРНУЮ доставку, а не пропущенную — недодрейненный хвост новая попытка не перечитывает, её курсор начинается с размера журнала на допуске | приёмка D39.131 |
|
||||
| П-14 | **ИСПОЛНЕНО P6 (14.08).** Стройка интейка формы Б (ратификация D39.130) | ИСПОЛНЕНО P6: рендер в ОДНОМ месте (`books.provision`), `TM_PLATFORM_BOOK_TEMPLATE`, работа с YAML-узлом (комментарии и незнакомые ключи оператора живы, значения — строки), `O_EXCL` (никогда не перезаписываем), битый шаблон = класс, который ЖДЁТ. Требования к полям — `backend/internal/config/book.go` как справочник; шаблон — деплой-артефакт оператора, не код. При приходе формы В (строка 170 единого, `tmctl init`) рендер заменяется вызовом движка. Проверено настоящим `tmctl manifest` (3 главы) | ратификация D39.130 (приёмка P5) |
|
||||
| П-16 | **ИСПОЛНЕНО P6 (14.08).** Дев-сид стенда: тестовый пользователь, чтобы фронт разрабатывался на живой платформе, а не на своих моках (слово владельца 14.08). Границы: фикстуры фронта остаются батарее его гейтов (детерминированные состояния — не работа стенда); полный снос `frontend/src/mock/` — триггер Ф-29, после читающей поверхности | ИСПОЛНЕНО P6: `tmplatformctl seed` (дев-вход → аккаунт → грант → загрузка через ЖИВОЙ `POST /v0/books` → ожидание конца интейка), дев-вход `TM_PLATFORM_DEV_LOGIN` с четырьмя гардами непроходимости в проде, рецепт страницей в `deploy/README.md`. Прогнано на стенде целиком | владелец 14.08 |
|
||||
| П-17 | **ИСПОЛНЕНО P7 (16–17.08).** Читающая поверхность — остаток П-1, названный отдельной строкой | ИСПОЛНЕНО P7: `listChapters`/`listUnits`/`listNotes`/`listBankTerms`/`streamBookEvents`/`getCapabilities` по канону 0.3.0 · модель ошибок с машинным `code` · условные чтения и сжатие · `Idempotency-Key` · `structure_version` · материализатор `internal/readmodel` (манифест + экспорт + сайдкар банка). ⚠ `submitBankDecisions` СНЯТ 22.08 вместе с пер-термной моделью подписи — D39.144, слово владельца; см. PD-370. **НЕ вошло и отложено в P8:** `updateBook`/`deleteBook`/`getRun`, экспорт (`createExport`/`getExport`), эскроу (П-18) | П-1, D39.84/85, контракт 14 |
|
||||
| П-14 | **ИСПОЛНЕНО P6 (14.08).** Стройка интейка формы Б (ратификация D39.130) | ИСПОЛНЕНО P6: рендер в ОДНОМ месте (`books.provision`); правила шаблона и его отказов — `deploy/README.md` §«Шаблон книги», требования к полям — `backend/internal/config/book.go` как справочник. При приходе формы В (строка 170 единого, `tmctl init`) рендер заменяется вызовом движка. Проверено настоящим `tmctl manifest` (3 главы) | ратификация D39.130 (приёмка P5) |
|
||||
| П-16 | **ИСПОЛНЕНО P6 (14.08).** Дев-сид стенда: тестовый пользователь, чтобы фронт разрабатывался на живой платформе, а не на своих моках (слово владельца 14.08). Границы: фикстуры фронта остаются батарее его гейтов (детерминированные состояния — не работа стенда); полный снос `frontend/src/mock/` — триггер Ф-29, после читающей поверхности | ИСПОЛНЕНО P6: `tmplatformctl seed` и дев-вход `TM_PLATFORM_DEV_LOGIN` с четырьмя гардами непроходимости в проде — рецепт и разбор гардов в `deploy/README.md` §«Дев-стенд». Прогнано на стенде целиком | владелец 14.08 |
|
||||
| П-17 | **ИСПОЛНЕНО P7 (16–17.08).** Читающая поверхность — остаток П-1, названный отдельной строкой | ИСПОЛНЕНО P7 по канону 0.3.0; состав поверхности — `platform/README.md` §«Что здесь будет» и §«Карта зоны» (`internal/readmodel`). ⚠ `submitBankDecisions` СНЯТ 22.08 вместе с пер-термной моделью подписи — D39.144, слово владельца; см. PD-370. **НЕ вошло и отложено в P8:** `updateBook`/`deleteBook`/`getRun`, экспорт (`createExport`/`getExport`), эскроу (П-18) | П-1, D39.84/85, контракт 14 |
|
||||
| П-18 | **Эскроу денег шва: write-ahead intent · `uncertain` · `closing`** (строка 136 единого бэклога) — денежный промт, сознательно НЕ взятый ни P5, ни P6. Здесь же закрывается PD-154 (`settled_at` между `Settle` и `MarkSettled`): половина эскроу рядом с проектируемым целым — второй, более слабый ответ на тот же вопрос | денежный промт | строка 136, PD-154 |
|
||||
| П-19 | **Конверсия свободного от склейки блока под `sqlc`** (решение владельца 20.08 «БЕРЁМ»; граница названа P8-FIX, PD-44). Пять файлов без единой склейки — `credits.go`(15) · `identity.go`(13) · `idempotency.go`(7) · `sessions.go`(5) · `observe.go`(1) = **42 места / 41 текст / 40 КОНВЕРТИРУЕМЫХ** (⚠ испр. D39.172: `observe.go` структурно не конвертируется — River мигрирует `river_job` сам; худший позиционный дрейф набора, семь `int64` подряд, остался рукописным): это весь кусок пакета, где инструмент силён и ничего не ломает. Остальные 25 склеенных мест недостижимы по построению (`lastRun` — девять потребителей, `nextRevisionOfThisBooksLibrary` — восемь), и это ровно та часть, где рантайм-ошибки и случались. Состав пака: `sqlc.yaml` + пин версии инструмента (как у `golangci-lint`) + генерённый код в дереве + гейт «сгенерённое актуально» + `WithTx` для одиннадцати денежных запросов, идущих внутри ЧУЖОЙ транзакции. ⚠ Класс «нет такой колонки» УЖЕ закрыт постоянным гейтом (`TestEverySQLStatementParsesAgainstTheMigratedSchema`, 162 оператора, включая склеенные), поэтому этот пак покупает типизированные скан-структуры и раннюю обратную связь, а не корректность. **РЕШЕНО владельцем 22.08: ОТДЕЛЬНОЙ СЕССИЕЙ, не внутри пака** — изменений много, мешать их с чем-либо нельзя | отдельная сессия | PD-44, фикс-лист приёмки P7 п. 4, решение владельца 22.08 |
|
||||
| П-20 | **Хвост PD-162 после P8-FIX: холд прогона, чей движок не ответит НИКОГДА, возвращается только рукой.** Пак дал оператору и диагноз (`tmplatformctl runs --stalled`, гейдж `tm_platform_runs_stalled`), и терминальный вердикт (`run abandon [--release-hold]`), и это сознательная граница: автоматически решать про деньги прогона, о котором нельзя спросить, — та же ошибка «я не смог спросить = его нет», только со счётчиком впереди. Автоматический ответ на этот вопрос и есть эскроу — write-ahead intent · `uncertain` · `closing` (строка 136 единого бэклога, П-18). Пока эскроу нет, ручка остаётся ручной, и это записано, а не подразумевается | вместе с П-18 | P8-FIX |
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
# platform — control plane (SaaS-слой)
|
||||
|
||||
Зона записи сессии «Платформа». Что построено и каким паком — секция «Что здесь будет» ниже и
|
||||
шапка «Текущее состояние» зонного журнала. Направление зоны — `docs/PLATFORM_DIRECTION.md`, критерии приёмки —
|
||||
`docs/ENGINEERING_STANDARDS.md`, дефекты — `docs/DEFECT_REGISTER.md`, зонный журнал —
|
||||
`docs/platform-PROGRESS.md` (весь прогресс зоны здесь, решение владельца 04.08), стек —
|
||||
шапка «Текущее состояние» зонного журнала. Направление зоны — `docs/PLATFORM_DIRECTION.md`,
|
||||
критерии приёмки — `docs/ENGINEERING_STANDARDS.md`, дефекты — `docs/DEFECT_REGISTER.md`, зонный
|
||||
журнал — `docs/platform-PROGRESS.md` (весь прогресс зоны здесь, решение владельца 04.08), стек —
|
||||
`docs/STACK_DECISIONS.md`.
|
||||
|
||||
Батарея зоны: `make check` (build · vet · fmt · lint · test -race). `make vuln` и `make fuzz` —
|
||||
|
|
@ -19,9 +19,8 @@
|
|||
её отвечает `GET /capabilities`, по ней же интейк отклоняет неподдерживаемую пару кодом
|
||||
`unsupported_pair`. Какие пары существуют, решают ДАННЫЕ (пакет промптов оператора), а не список в Go.
|
||||
⚠ **Пустой список отклоняет ВСЁ, а не пропускает всё** (акт 5 P7): «не объявлено ничего» и «объявлена
|
||||
эта пара» — разные ответы, и прежнее послабление заставляло деплой принимать книги, которые он мог
|
||||
только провалить. Инстанс, принимающий загрузки без единой ДОСТУПНОЙ пары, не стартует вовсе — это
|
||||
второй конец того же правила, и он в силе и в бою, и в рецепте дев-стенда.
|
||||
эта пара» — разные ответы. Инстанс, принимающий загрузки без единой ДОСТУПНОЙ пары, не стартует
|
||||
вовсе — это второй конец того же правила, и он в силе и в бою, и в рецепте дев-стенда.
|
||||
|
||||
Бинари: `cmd/tmplatformd` (сервис) и `cmd/tmplatformctl` (админ: гранты, КОРРЕКТИРОВКИ (`adjust`), баланс, журнал входов,
|
||||
отзыв сессий, дев-интейк `book add`, список книг для апгрейда движка `books` и список книг, чью
|
||||
|
|
@ -122,20 +121,16 @@ Prometheus; на контрактную поверхность они не вы
|
|||
| `internal/pricing`, `internal/money` | шкала глав и целые микро-доллары |
|
||||
| `internal/metrics`, `internal/reqid`, `internal/jobs`, `internal/config`, `internal/gates` | телеметрия, id запроса, очередь, конфигурация, гейты тулчейна |
|
||||
|
||||
Каналы движка, которыми зона пользуется, — СЕМЬ. ⚠ Слов «и других нет» здесь нет намеренно:
|
||||
исчерпывающий перечень с атомарностью живёт в `docs/STACK_DECISIONS.md`, «Инвентарь каналов
|
||||
движка», и список ниже СЧИТАЕТСЯ при правке вместе с ним. `tmctl manifest --json` (структура),
|
||||
`tmctl export --json --pairs`
|
||||
(ТЕКСТ пар — единственный носитель), `<project_db>.bank.json` (банк), `tmctl status --json` (канал
|
||||
ремонта; с лендингом `6ec9f8a` он ещё и оценивает пере-проход ДО покупки), `events.jsonl` (поток) и —
|
||||
с лендингом пака «писатель книги» (D39.175) — `tmctl build` с файловым сайдкаром
|
||||
`<project_db>.book.<fmt>`, чьи пути публикуются в `StatusArtifacts.book_files`. Шестой канал ещё никем
|
||||
в зоне не потребляется: его будет читать дверь выдачи `createExport`/`getExport`, и она обязана
|
||||
СТРОИТЬ (звать `tmctl build`), а не подбирать лежащий рядом файл — там копия прежней сборки
|
||||
(D39.175 п.2). Седьмой — `tmctl bank-apply`: документ решений уходит движку файлом, отчёт
|
||||
`tm-bank-report-v1` приходит обратно на stdout; это ЕДИНСТВЕННЫЙ канал, по которому зона ПИШЕТ в
|
||||
проект движка, и потому у него своя дверь (`POST /books/{bookId}/bank/corrections`) и свой класс
|
||||
отказов. Живой SQLite движка не читается никогда (D39.85).
|
||||
Каналы движка, которыми зона пользуется, — СЕМЬ: `tmctl manifest --json` ·
|
||||
`tmctl export --json --pairs` · `<project_db>.bank.json` · `tmctl status --json` · `events.jsonl` ·
|
||||
`tmctl build` · `tmctl bank-apply`. ⚠ Слов «и других нет» здесь нет намеренно, и второй копии
|
||||
перечня здесь тоже нет: атомарность каждого канала, колонка «потребляет ли платформа» и все
|
||||
оговорки лежат ЕДИНСТВЕННЫМ носителем в `docs/STACK_DECISIONS.md`, «Инвентарь каналов движка». Оттуда же два правила, которые
|
||||
дороже перечня: `tmctl build` (D39.175) в зоне ещё НИКЕМ не читается, и его будущая дверь выдачи
|
||||
`createExport`/`getExport` обязана СТРОИТЬ, а не подбирать лежащий рядом файл — там копия прежней
|
||||
сборки (D39.175 п.2); `tmctl bank-apply` — ЕДИНСТВЕННЫЙ канал, по которому зона ПИШЕТ в проект
|
||||
движка, и потому у него своя дверь (`POST /books/{bookId}/bank/corrections`) и свой класс отказов.
|
||||
Живой SQLite движка не читается никогда (D39.85).
|
||||
|
||||
## Чего здесь НЕ будет
|
||||
|
||||
|
|
@ -147,21 +142,21 @@ Prometheus; на контрактную поверхность они не вы
|
|||
|
||||
## Известное требование к движку (не забыть)
|
||||
|
||||
⚠ **Глобальный брокер конкурентности.** `pipeline/ratelimit.go` строит рейт-гарды НА ПРОГОН,
|
||||
а лимит провайдера — на весь аккаунт. N параллельных пользователей = N независимых гардов
|
||||
против общего лимита (в комментарии там зафиксировано: mistral валит ~48% вызовов под
|
||||
параллелизмом). Воркер должен отпрашиваться у платформы перед вызовом. Это единственная
|
||||
по-настоящему новая механика на стыке.
|
||||
⚠ **Глобальный брокер конкурентности** — единственная по-настоящему новая механика на стыке:
|
||||
`pipeline/ratelimit.go` строит рейт-гарды НА ПРОГОН, а лимит провайдера — на весь аккаунт.
|
||||
Постановка, замер и вес — строка `BACKLOG.md` П-2.
|
||||
|
||||
## Стек
|
||||
|
||||
Пины, даты релизов и обоснования — [`docs/STACK_DECISIONS.md`](docs/STACK_DECISIONS.md) (зонный,
|
||||
live-сверка 04–05.08); общая записка по обоим новым сервисам — `../frontend/docs/STACK_DECISIONS.md` §5.
|
||||
|
||||
Коротко: Go 1.26.4 в `go.mod` — общий с движком floor ЯЗЫКА, при этом `toolchain go1.26.6` там же поднимает тулчейн (D39.130; `make version-check` СРАВНИВАЕТ версии, а не матчит) · стандартный `net/http` + `ServeMux` без
|
||||
роутер-библиотеки · PostgreSQL 18 · pgx v5.10.0 · goose v3.27.3 · очередь River v0.42.0 на том же
|
||||
Postgres — **подключена и работает** (P4) · вход `x/oauth2` v0.36.0 + `go-oidc/v3` v3.20.0 ·
|
||||
`x/time` v0.15.0 для лимита на `/auth/login` · `govulncheck` отдельной целью.
|
||||
Коротко: floor ЯЗЫКА в `go.mod` общий с движком, `toolchain` там же поднимает тулчейн выше
|
||||
(D39.130; `make version-check` СРАВНИВАЕТ версии, а не матчит) · стандартный `net/http` + `ServeMux`
|
||||
без роутер-библиотеки · PostgreSQL 18 + pgx · goose · очередь River на том же Postgres —
|
||||
**подключена и работает** (P4) · вход `x/oauth2` + `go-oidc/v3` · `x/time` для лимита на
|
||||
`/auth/login` · `govulncheck` отдельной целью. ⚠ Номера версий здесь намеренно не дублируются: их
|
||||
единственные носители — `go.mod` и таблица пинов строкой выше.
|
||||
**Redis не заводим нигде** — зафиксировано как архитектурное «нет».
|
||||
|
||||
Прогресс наружу — SSE, события **пушит воркер**, а не фронт опрашивает read-model.
|
||||
|
|
|
|||
|
|
@ -183,34 +183,34 @@ ceilings:
|
|||
|
||||
## Апгрейд ДВИЖКА: порядок против деадлока (строка 174 единого бэклога)
|
||||
|
||||
Read-only команды движка отказывают файлу проекта СТАРЕЕ бинаря — **exit 13 и токен
|
||||
`schema_mismatch found=N expected=M` на stderr** (форма финализирована D39.134; прежняя
|
||||
цитата «schema vN … expects vM» в этом доке была МЁРТВОЙ — движок её не печатает), а
|
||||
платформа зовёт `status` перед каждым спавном и на расчёте денег. Значит выкат нового
|
||||
движка на хост с существующими книгами запирает их до миграции файла проекта.
|
||||
Read-only команды движка отказывают файлу проекта СТАРЕЕ бинаря —
|
||||
**exit 13 и токен `schema_mismatch found=N expected=M` на stderr**
|
||||
(форма финализирована D39.134 п.4), а платформа зовёт `status` перед
|
||||
каждым спавном и на расчёте денег. Значит выкат нового движка на
|
||||
хост с существующими книгами запирает их до миграции файла проекта.
|
||||
|
||||
⚠ **Проверено исполнением на стенде (P7):** проектная БД, отведённая на схему v14 при
|
||||
бинаре v15, даёт `tmctl status --json` → exit 13 с этим токеном; `tmctl migrate
|
||||
--config <book.yaml>` делает пред-миграционный бэкап и переводит v14 → v15; тот же
|
||||
`status` после неё — exit 0.
|
||||
|
||||
⚠ **`tmctl migrate` СУЩЕСТВУЕТ и заленден** — $0-команда движка, открывающая файл
|
||||
проекта на запись без прогона: `backend/cmd/tmctl/migrate.go`, в диспетчере `main.go`,
|
||||
свой код выхода (`exitSchemaMismatch = 13`, «run `tmctl migrate`»). Порядок ниже —
|
||||
ДЕЙСТВУЮЩИЙ, а не «когда приедет».
|
||||
⚠ **`tmctl migrate` СУЩЕСТВУЕТ и заленден** — $0-команда
|
||||
движка, открывающая файл проекта на запись без прогона:
|
||||
`backend/cmd/tmctl/migrate.go`, в диспетчере `main.go`, свой
|
||||
код выхода (`exitSchemaMismatch = 13`, «run `tmctl migrate`»).
|
||||
|
||||
⚠⚠ **ВТОРАЯ, НЕЗАВИСИМАЯ причина не выкатывать движок раньше платформы: подъём ФОРМЫ
|
||||
МАНИФЕСТА движка требует платформенного билда, иначе выбывает КАЖДАЯ новая книга.**
|
||||
Платформа с P12 сверяет `manifest_version` с известной ей формой
|
||||
(`ingest.KnownManifestVersion`, зеркало `manifestVersion` движка) и на незнакомую
|
||||
отвечает НЕ-деструктивным классом `parser_unavailable` — файл пользователя цел, и это
|
||||
осознанный выбор: без сверки переименованный ключ декодируется в нули, а ноль глав
|
||||
интейк читает как «источник прочли, книги нет», то есть УДАЛЯЕТ аплоад (`PD-213`). Но
|
||||
класс всё равно ТЕРМИНАЛЕН по бюджету попыток: движок впереди платформы ⇒ каждая
|
||||
загруженная книга уходит в `rejected` после пяти попыток. Симптом — интейк массово
|
||||
отклоняет при здоровом на вид движке; лечение — выкатить платформенный билд, знающий
|
||||
новую форму. Правило то же, что у схемы хранилища: **сначала платформа, потом движок**,
|
||||
и оба конца этого правила теперь записаны.
|
||||
⚠⚠ **ВТОРАЯ, НЕЗАВИСИМАЯ причина не выкатывать движок раньше платформы: подъём
|
||||
ФОРМЫ МАНИФЕСТА движка требует платформенного билда, иначе выбывает КАЖДАЯ
|
||||
новая книга.** Платформа с P12 сверяет `manifest_version` с известной ей
|
||||
формой (`ingest.KnownManifestVersion`, зеркало `manifestVersion` движка) и на
|
||||
незнакомую отвечает НЕ-деструктивным классом `parser_unavailable` — файл
|
||||
пользователя цел, и это осознанный выбор: без сверки переименованный ключ
|
||||
декодируется в нули, а ноль глав интейк читает как «источник прочли, книги
|
||||
нет», то есть УДАЛЯЕТ аплоад (`PD-213`). Но класс всё равно ТЕРМИНАЛЕН по
|
||||
бюджету попыток: движок впереди платформы ⇒ каждая загруженная книга уходит в
|
||||
`rejected` после пяти попыток. Симптом — интейк массово отклоняет при здоровом
|
||||
на вид движке; лечение — выкатить платформенный билд, знающий новую форму.
|
||||
Правило то же, что у схемы хранилища: **сначала платформа, потом движок**.
|
||||
|
||||
Порядок (действующий). ⚠ Ключевое: **`migrate` гоняется НОВЫМ бинарём** — старый уводит
|
||||
файл в свою же схему, то есть не делает ничего, и деадлок остаётся. Поэтому бинарь
|
||||
|
|
@ -359,9 +359,8 @@ export TM_PLATFORM_STATE_DIR=$HOME/.local/share/tmstand/state
|
|||
export TM_PLATFORM_ENGINE_BIN=$HOME/.local/bin/tmctl
|
||||
export TM_PLATFORM_CTL_BIN=$PWD/tmplatformctl # абсолютный: systemd отвергает иной
|
||||
export TM_PLATFORM_BOOK_TEMPLATE=$HOME/.local/share/tmstand/book-template.yaml
|
||||
# ⚠ ОБЯЗАТЕЛЬНО, как и в бою: инстанс, принимающий загрузки, обязан объявить хотя бы одну ДОСТУПНУЮ
|
||||
# пару, иначе демон отказывается стартовать. Пустой список — это «не объявлено ничего», а не
|
||||
# «пускать всё»: без гейта стенд принимал книги, которые мог только провалить.
|
||||
# ⚠ ОБЯЗАТЕЛЬНО, как и в бою: без хотя бы одной ДОСТУПНОЙ пары демон, принимающий загрузки, не
|
||||
# стартует. Правило и его причина — блок ⚠ `TM_PLATFORM_LANGUAGE_PAIRS` выше.
|
||||
export TM_PLATFORM_LANGUAGE_PAIRS='zh>ru'
|
||||
mkdir -p "$TM_PLATFORM_BOOKS_DIR" "$TM_PLATFORM_STATE_DIR"
|
||||
|
||||
|
|
@ -396,11 +395,9 @@ await fetch('/auth/dev-login', {method: 'POST'})
|
|||
install -d -m0750 -o tmplatform -g tmplatform /srv/textmachine/books
|
||||
```
|
||||
|
||||
⚠ **`/metrics` не аутентифицирован** — его защищает только адрес привязки. Дефолт `127.0.0.1`, то
|
||||
есть снаружи он недостижим; поднять его на внешний адрес значит открыть операционную форму деплоя
|
||||
(глубина очереди, число прогонов, возраст холдов) всем, кто до него дотянется. Скрейпер живёт на том
|
||||
же хосте либо ходит через тот же edge, что и API. Разбор — `docs/STACK_DECISIONS.md` §24, строка
|
||||
риска — PD-179.
|
||||
⚠ **`/metrics` не аутентифицирован** — его защищает только адрес привязки, и дефолт `127.0.0.1`
|
||||
снаружи недостижим. Скрейпер живёт на том же хосте либо ходит через тот же edge, что и API; что
|
||||
именно открывает внешний адрес и почему риск принят — `PD-179`, разбор `docs/STACK_DECISIONS.md` §24.
|
||||
|
||||
⚠ **Загрузка книги идёт минуты, и это касается edge-прокси.** Маршрут `POST /v0/books` принимает до
|
||||
`TM_PLATFORM_MAX_UPLOAD_BYTES` (64 МиБ) и держит соединение до `TM_PLATFORM_UPLOAD_DEADLINE`
|
||||
|
|
@ -420,11 +417,10 @@ install -d -m0750 -o tmplatform -g tmplatform /srv/textmachine/books
|
|||
## Где на сервере лежат книги
|
||||
|
||||
`/srv/textmachine` — корень библиотеки НА СЕРВЕРЕ. Решение владельца «книги живут в `~/books`»
|
||||
относится к машине разработки: под этим юнитом домашние каталоги недоступны вовсе
|
||||
(`ProtectHome=yes` подставляет пустой `/home` и детям-`tmctl` тоже), поэтому библиотека под
|
||||
домашним каталогом на сервере просто не откроется — не «сработает медленнее», а не найдётся.
|
||||
Оператору, которому это нужно, юнит называет ровно две строки замены (`ProtectHome=tmpfs` +
|
||||
`BindPaths=`); других изменений не требуется.
|
||||
относится к машине разработки: под этим юнитом домашние каталоги недоступны и детям-`tmctl` тоже
|
||||
(`ProtectHome=yes`, проверено живым прогоном — §«Файлы»), поэтому библиотека под домашним каталогом
|
||||
на сервере не «сработает медленнее», а НЕ НАЙДЁТСЯ. Кому это нужно, юнит называет ровно две строки
|
||||
замены (`ProtectHome=tmpfs` + `BindPaths=`); других изменений не требуется.
|
||||
|
||||
## Чего здесь ещё нет
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -42,8 +42,7 @@
|
|||
|
||||
**Наблюдаемость** — базовая линия **практики именования Prometheus** (базовые единицы: секунды и
|
||||
байты; `_total` у счётчиков; единица в имени, не в лейбле) + **четыре золотых сигнала** SRE на
|
||||
вопрос «что мерить» (внесено P5 по ратифицированному направлению PD-115: у оси появился внешний
|
||||
эталон, а не только собственная проза зоны). Конкретика:
|
||||
вопрос «что мерить» (`PD-115`). Конкретика:
|
||||
- структурные логи (route-pattern, не сырой путь — id пользователя/книги в логи не текут),
|
||||
`X-Request-Id` на каждом ответе, ошибки видимы на своём уровне;
|
||||
- метрики отдаются в формате Prometheus на ОТДЕЛЬНОМ слушателе (`STACK_DECISIONS` §24); лейбл несёт
|
||||
|
|
|
|||
|
|
@ -50,9 +50,7 @@ hosted IdP (связка PII + доступность, а свою сессию
|
|||
п.2л, PD-104): дефолт `TM_PLATFORM_SIGNUP_GRANT_USD` = **0**, кредит начисляется руками
|
||||
(`tmplatformctl grant`); возврат автоматического гранта идёт ВМЕСТЕ с суточным агрегатным потолком,
|
||||
который его ограничивает, а тот принадлежит платежам. Платёжный провайдер не выбирается, не проектируется и в бэклог зоны как
|
||||
работа не заходит; вернуться — когда появится решение продавать. ⚠ Прежняя редакция этого раздела
|
||||
несла вывод «покупателям из России платить нечем» — он был ВЫВЕДЕН из целевого языка перевода, а не
|
||||
установлен, и снят как необоснованный (владелец 05.08). Язык книги о географии плательщика не говорит.
|
||||
работа не заходит; вернуться — когда появится решение продавать.
|
||||
|
||||
**Модель лимита — БАЛАНС кредитов, а не окна с обнулением** (владелец 05.08: «не подписки, а покупка
|
||||
токенов как у OpenRouter»). Следствия, несущие для схемы и контракта:
|
||||
|
|
@ -69,7 +67,7 @@ hosted IdP (связка PII + доступность, а свою сессию
|
|||
фри-тир. Нужна минимальная админ-поверхность — защищённая ручка или CLI-команда, пишущая грант.
|
||||
|
||||
**Схема (строить с П-7):** `credit_ledger` (append-only, знаковые целые микро-доллары, типы
|
||||
`grant|hold|hold_release|settlement|adjustment`; `UNIQUE(user_id, source, source_id)` — ключ идемпотентности; ⚠ первая редакция этого абзаца называла `UNIQUE(source, source_id)`, и это ошибка: без `user_id` ключ, потраченный на одном аккаунте, проглатывает тот же ключ на другом, и второму сообщают «начислено», не начислив ничего (миграция `00007` и её комментарий);
|
||||
`grant|hold|hold_release|settlement|adjustment`; `UNIQUE(user_id, source, source_id)` — ключ идемпотентности, скоупленный АККАУНТОМ (почему в нём обязателен `user_id` — комментарий миграции `00007_credits.sql`);
|
||||
`purchase` добавится, если появится продажа), `reservations` (одна открытая на `engine_run_id`),
|
||||
`account_balances` (кэш в ТОЙ ЖЕ транзакции, что вставка в леджер, + тест-инвариант
|
||||
`balance == SUM(ledger)`). Правки строк не существует: ошибка чинится новой записью. Только целые
|
||||
|
|
@ -78,13 +76,12 @@ hosted IdP (связка PII + доступность, а свою сессию
|
|||
**Защита и свежесть — ДВА РАЗНЫХ механизма, строим ОБА (решение владельца 05.08 «сделаем нормально»;
|
||||
моё прочтение его слов — записано так, чтобы дешёво поправить, если прочитал не то):**
|
||||
|
||||
1. **Защита — холд + потолок движку (было «вариант B»).** В той же транзакции, что допускает задачу
|
||||
в очередь, ДО спавна `tmctl` ставим холд и передаём движку пер-книжный потолок ≤ остатка баланса.
|
||||
1. **Защита — холд + потолок движку.** В той же транзакции, что допускает задачу в очередь, ДО спавна `tmctl` ставим холд и передаём движку пер-книжный потолок ≤ остатка баланса.
|
||||
Жёсткий стоп исполняет САМ движок, у которого механизм уже есть ⇒ перерасход физически невозможен,
|
||||
даже если платформа во время прогона слепа. Это рубеж корректности, и он НЕ зависит от доставки
|
||||
событий (поток at-least-once и на краше теряет хвост).
|
||||
2. **Свежесть — версионированное денежное событие в словаре потока (было «вариант A»).** Движок
|
||||
эмитит НАКОПИТЕЛЬНЫЙ счётчик потраченного на границах стадии/волны; платформа материализует его
|
||||
2. **Свежесть — версионированное денежное событие в словаре потока.** Движок эмитит
|
||||
НАКОПИТЕЛЬНЫЙ счётчик потраченного на границах стадии/волны; платформа материализует его
|
||||
существующим идемпотентным апсертом `(engine_run_id, seq)`. Накопительная семантика делает
|
||||
повтор и дубль безвредными (берём максимум по прогону). Индикатор остатка перестаёт отставать
|
||||
на целую попытку.
|
||||
|
|
@ -100,16 +97,11 @@ hosted IdP (связка PII + доступность, а свою сессию
|
|||
## 3. Стандарты и стек: что взять, что не брать
|
||||
|
||||
> ⚠⚠ **ВЕРХНИЙ СЛОЙ — ЧИТАТЬ ПРЕЖДЕ ВСЕГО НИЖЕ. По `sqlc` действует НЕ то решение, что записано
|
||||
> в этом разделе:** владелец 20.08 сказал **БЕРЁМ** (D39.153 п.6а), 22.08 уточнил — отдельной
|
||||
> сессией (D39.154 п.10), и 29.08 пак **принят и заленджен** (`D39.172`): сорок запросов пяти файлов
|
||||
> `pgstore` вынесены в `internal/pgstore/queries/`, пин 1.31.1, `sqlc diff` в `make check`. Граница
|
||||
> узкая по построению, а не по вкусу: свободный от склейки блок; остальное (25 склеек из 147, 15
|
||||
> фрагментов-констант, read-модель) для sqlc недостижимо. Устройство, оговорки и цена —
|
||||
> `STACK_DECISIONS.md`, строка «Кодоген SQL». Отказ P7 ниже остаётся как история пака, а не как
|
||||
> действующее решение. ⚠ Тем же паком построена ЗАМЕНА того, ради чего sqlc
|
||||
> звали: гейт `pgstore.TestEverySQLStatementParsesAgainstTheMigratedSchema` планирует ЖИВЫМ Postgres
|
||||
> каждый собранный запрос пакета против мигрированной схемы — то есть покрывает и склейки, которых
|
||||
> sqlc не видит, и именно в них случились оба рантайм-падения зоны.
|
||||
> в этом разделе:** владелец сказал **БЕРЁМ** (`D39.153` п.6а, уточнение `D39.154` п.10), и 29.08
|
||||
> пак **принят и заленджен** (`D39.172`). Устройство, пин и цена — `STACK_DECISIONS.md`, строка
|
||||
> «Кодоген SQL»; граница инструмента и построенная взамен замена (гейт планируемости КАЖДОГО SQL
|
||||
> пакета, включая склейки) — `DEFECT_REGISTER.md`, `PD-44`. Отказ P7 ниже остаётся как история
|
||||
> пака, а не как действующее решение.
|
||||
>
|
||||
> ⚠ **ПЕРЕ-ПОДПИСАНО D39.132 п.2б:** `oapi-codegen` — КАНДИДАТ, решение за паком, который его
|
||||
> берёт. **Решение P7 по обоим — НЕ БРАТЬ, с доводом, а не молчанием (история пака P7, 20.08):**
|
||||
|
|
@ -129,7 +121,7 @@ hosted IdP (связка PII + доступность, а свою сессию
|
|||
> тестами (`httpapi.Test*CarriesEveryRequiredField`).
|
||||
|
||||
Стек менять не надо: правильные куски уже взяты (pgx · goose · River · stdlib ServeMux/CSRF · slog ·
|
||||
опаковые сессии). Добавить четыре вещи, пока репозиторий — скелет:
|
||||
опаковые сессии). Четыре добавления и их сегодняшний статус:
|
||||
|
||||
| Добавление | Пин | Статус |
|
||||
|---|---|---|
|
||||
|
|
@ -169,39 +161,30 @@ zonky-стенд Postgres без root, пины линтера/govulncheck, из
|
|||
feature-flag-платформы · CodeQL (на приватном репо платный) · gosec отдельным гейтом · SBOM/SLSA —
|
||||
церемония на этом размере.
|
||||
|
||||
**Базовые линии безопасности:** OWASP **ASVS 5.0.0**, целевой **L2** — ⚠ поправка скептика к карте
|
||||
глав: в 5.0 сессии это **V7**, аутентификация **V6**, и есть НОВАЯ глава **V10 «OAuth and OIDC»** —
|
||||
именно она написана под наше направление (ресёрч цитировал нумерацию 4.0 и главу V10 пропустил).
|
||||
Плюс OWASP API Security Top 10 (2023) — API1 BOLA это буквально проверка владения книгой на каждом
|
||||
`/v0`-маршруте, API4 — наш PD-2. Плюс уже действующие govulncheck-гейт и пины Go-модулей с sumdb.
|
||||
Dependabot — да (граф крошечный, шума нет).
|
||||
**Базовые линии безопасности** — `ENGINEERING_STANDARDS.md` §2. ⚠ Поправка скептика, из-за которой
|
||||
карта глав там именно такая: ресёрч цитировал нумерацию ASVS 4.0 и пропустил НОВУЮ главу **V10
|
||||
«OAuth and OIDC»**, написанную ровно под наше направление. Сверх стандарта: govulncheck-гейт, пины
|
||||
Go-модулей с sumdb, Dependabot — да (граф крошечный, шума нет).
|
||||
|
||||
**Тест-пол — тот, что поймал бы дефекты этой приёмки** (иначе стандарт бесполезен):
|
||||
1. Тест на РЕАЛЬНО сконфигурированном `http.Server`, а не на mux под `httptest`: полу-кормленный POST
|
||||
обязан получить закрытие по `ReadTimeout`; SIGTERM обязан дренировать запросы (сегодня падают оба —
|
||||
PD-2 и PD-9; ни один mux-тест этот класс не видит).
|
||||
2. Инвариант сессии НЕЗАВИСИМЫМ оракулом: SHA-256 считается ВНЕ `auth.Digest`, плюс ассерт «плейнтекст
|
||||
токена в БД не находится» (PD-1: свойство истинно, но моя посадка его не уронила).
|
||||
3. Нативный фаззинг `go test -fuzz` на NDJSON-декодере, оракулы — инварианты PD-10.
|
||||
4. Дисциплина посадок мутаций (`ENGINEERING_STANDARDS.md` §3.3) как постоянная проверка того, что
|
||||
свойства ЗАПИНЕНЫ, а не просто истинны.
|
||||
5. Контрактные тесты против OpenAPI-спеки и нагрузочные (k6) — при первых живых ручках, не раньше.
|
||||
**Тест-пол.** Четыре его пункта исполнены и живут нормой, а не планом: тест на РЕАЛЬНО
|
||||
сконфигурированном `http.Server` вместо mux под `httptest` (`PD-2`, `PD-9`), инвариант сессии
|
||||
НЕЗАВИСИМЫМ оракулом (`PD-1`), фаззинг NDJSON-декодера (`PD-10`) — эти строки регистра `fixed`,
|
||||
каждая со своим пином; дисциплина посадок мутаций — `ENGINEERING_STANDARDS.md` §3. Полом остаётся
|
||||
только пятый пункт: контрактные тесты против OpenAPI-спеки и нагрузочные (k6) — при первых живых
|
||||
ручках, не раньше.
|
||||
|
||||
**Деплой:** одна VM + systemd-юниты, бинари артефактами CI; Postgres сперва там же, управляемый — при
|
||||
первой выручке. Не Kubernetes: дети-`tmctl` живут ЧАСАМИ, держат эксклюзивный лок на файлах книги на
|
||||
локальном диске, и любой оркестратор, способный переселить под посреди прогона, нам враждебен.
|
||||
Юниты писать сейчас — они же честный ответ на PD-13 (осиротевшие процессы движка): прогон в
|
||||
cgroup-поддереве платформы, `KillMode=mixed`, `Restart=on-failure`, `LoadCredential=` для DSN и
|
||||
клиентского секрета OAuth, `MemoryMax`, песочница `ProtectSystem`/`PrivateTmp` бесплатно.
|
||||
Юниты написаны — `deploy/tmplatformd.service`, и они же ответ на `PD-13` (осиротевшие процессы
|
||||
движка); cgroup-поддерево прогона — `STACK_DECISIONS.md` §15–16.
|
||||
|
||||
## 4. Скорость: что это требует от платформы и контракта
|
||||
|
||||
Замер снял главный страх: **пер-главный объём крошечный** — ~3.4 тыс. символов исходника + ~10.2 тыс.
|
||||
перевода ≈ 27 КБ строк UTF-16 на главу, а контракт уже читает юниты ПО ГЛАВЕ, читает банк ДЕЛЬТОЙ
|
||||
(`?after_version=`) и отдаёт экспорт ссылкой. ⚠ Прежняя формулировка «подписывает банк частями»
|
||||
ЛОЖНА с D39.144: подпись — ОДИН акт над всем банком, а частичным является накопление РЕШЕНИЙ, что
|
||||
совсем другая вещь. Риск не в экране чтения, а в четырёх местах, где сегодняшняя форма
|
||||
вынудила бы клиента держать книгу целиком. Требования, которые платформа обязана исполнить:
|
||||
(`?after_version=`) и отдаёт экспорт ссылкой. Риск не в экране чтения, а в четырёх местах, где
|
||||
сегодняшняя форма вынудила бы клиента держать книгу целиком. Требования, которые платформа обязана исполнить:
|
||||
|
||||
1. **Сжатие ответов — ИСПОЛНЕНО P7** и записано нормой в канон 0.3.0, а не в этот док: JSON сжимается
|
||||
там же, где строится тело (`internal/httpapi/conditional.go`), `text/event-stream` — никогда, и
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
# Стек платформы — пины и обоснования
|
||||
|
||||
> Зонный документ `platform/`. Пины сверены ЖИВЬЁМ 04.08 и 05.08.2026 (Go-прокси `@latest`,
|
||||
> postgresql.org, go.dev/dl) — версии по памяти не называются. Библиотеки сессия не ратифицирует:
|
||||
> таблица уходит оркестратору вместе с деревом.
|
||||
> postgresql.org, go.dev/dl) — версии по памяти не называются. Порядок ратификации новой
|
||||
> зависимости — `ENGINEERING_STANDARDS.md` §1.
|
||||
>
|
||||
> Общая записка по обоим новым сервисам — `frontend/docs/STACK_DECISIONS.md` §5 (02.08). Здесь —
|
||||
> платформенная часть с датами релизов и сверкой; floor `go.mod` оставлен общим с движком, тулчейн
|
||||
> сборки поднят выше — см. таблицу.
|
||||
> Общая записка по обоим новым сервисам — `frontend/docs/STACK_DECISIONS.md` §5 (02.08); здесь —
|
||||
> платформенная часть с датами релизов и сверкой.
|
||||
|
||||
## Пины
|
||||
|
||||
|
|
@ -19,9 +18,9 @@
|
|||
| CSRF | stdlib `http.CrossOriginProtection` | Go 1.25 | Ровно тот механизм, что описан в §5 (Sec-Fetch-Site → Origin), теперь в тулчейне — свой велосипед не пишем |
|
||||
| Postgres-драйвер | `github.com/jackc/pgx/v5` **v5.10.0** | 03.06.2026 | Живой pool, `pgconn.PgError` для проверки констрейнтов, `stdlib` для goose |
|
||||
| Миграции | `github.com/pressly/goose/v3` **v3.27.3** | 22.07.2026 | Библиотекой + `embed.FS`; `WithSessionLocker` = advisory-лок, две реплики выкатываются по очереди |
|
||||
| Очередь | `github.com/riverqueue/river` **v0.42.0** | 31.07.2026 | В `go.mod` и в работе с P4 (§19): задание очереди выдаёт только РАЗРЕШЕНИЕ стартовать, а жизнь прогона ведёт реконсилятор. Мигрируется своим мигратором — две летописи в одной базе |
|
||||
| OIDC-вход | `golang.org/x/oauth2` **v0.36.0** + `github.com/coreos/go-oidc/v3` **v3.20.0** | 11.02.2026 · 08.07.2026 | Ратифицировано `PLATFORM_DIRECTION.md` §1; сверено живьём 05.08. Протокольный риск (PKCE, JWKS с рефетчем по kid, проверка подписи/issuer/audience/exp) отдан библиотекам, интеграция и модель аккаунта — наши. Транзитивно приходит `go-jose/v4` v4.1.4 |
|
||||
| Рейт-лимит в процессе | `golang.org/x/time` **v0.15.0** | 11.02.2026 | `rate.Limiter` на ОБЕИХ неаутентифицированных ручках, которые ПИШУТ: `/auth/login` (строка состояния) и `/auth/callback` (строка журнала на каждом отказе — замерено ~880 строк/с с одного хоста, пока лимита не было). Долговечные пер-пользовательские лимиты — в Postgres, когда появятся |
|
||||
| Очередь | `github.com/riverqueue/river` **v0.42.0** | 31.07.2026 | В `go.mod` и в работе с P4; устройство и цена формы — §19 |
|
||||
| OIDC-вход | `golang.org/x/oauth2` **v0.36.0** + `github.com/coreos/go-oidc/v3` **v3.20.0** | 11.02.2026 · 08.07.2026 | Ратифицировано `PLATFORM_DIRECTION.md` §1 (там же — что отдано библиотекам и что пишем сами); сверено живьём 05.08. Транзитивно приходит `go-jose/v4` v4.1.4 |
|
||||
| Рейт-лимит в процессе | `golang.org/x/time` **v0.15.0** | 11.02.2026 | `rate.Limiter` на ОБЕИХ неаутентифицированных ручках, которые ПИШУТ: `/auth/login` (строка состояния) и `/auth/callback` (строка журнала на каждом отказе — замерено ~880 строк/с с одного хоста, пока лимита не было) |
|
||||
| Линтер | `golangci-lint` **2.12.2** | 06.05.2026 | Тот же пин, что у движка: находки версионно-зависимы, разъезд пинов = разные гейты в одном репо |
|
||||
| Кодоген SQL | `sqlc` **v1.31.1** | 22.04.2026 | **Инструмент разработчика, НЕ зависимость модуля** — в `go.mod` не входит, рантайм-граф не растёт ни на один пакет; пин держит `make tools-check`, как у линтера, и по более острой причине: генерённый код лежит В ДЕРЕВЕ, поэтому другая версия молча даёт другой диф и `sqlc diff` краснеет на чистом клоне. **Зачем:** генерирует слой запросов свободного от склейки блока `internal/pgstore` (40 запросов из `queries/*.sql`) — SQL и его `Scan` перестают быть двумя списками, которые сверяет человек ПО ПОЗИЦИИ. Взят по решению владельца 20.08 (`D39.153` п.6а, уточнение `D39.154`), носитель — `BACKLOG.md` П-19. ⚠ Довод «гейт `sqlgate_test.go` это уже закрыл» ПРОВЕРЕН и не подтвердился: гейт получает только СТРОКУ SQL и Go-сторону вызова не видит — шесть посаженных перестановок целей `Scan` и сломанных арностей ВЫЖИЛИ на полной батарее (замер 29.08, `platform-PROGRESS.md`). Схему читает ТОТ ЖЕ каталог `migrations/`, что гейтит манифест (второго носителя нет); деньги держит подстановочный override `*.*_micro_usd` → `money.MicroUSD` — без него генератор выдаёт голый `int64`, с ним снятие override становится ОШИБКОЙ СБОРКИ. Актуальность генерации гейчена дважды: `sqlc diff` в `make check` и `pgstore.TestEveryGeneratedQueryMatchesItsSourceFile` в батарее, который работает и без установленного sqlc |
|
||||
| Уязвимости | `govulncheck` **v1.6.0** | 09.07.2026 | Отдельная цель `make vuln`, не часть `check`: ей нужна сеть, а батарея обязана быть зелёной на голом клоне офлайн |
|
||||
|
|
@ -68,7 +67,7 @@
|
|||
> Значит `DownTo(<5)` на живой базе падает. Править `00005` нельзя — это и есть append-only, —
|
||||
> а новая миграция чужой down-текст не заменяет. Данные при этом целы: down транзакционный,
|
||||
> `Up()` возвращает схему на текущую версию (проверено прогоном: `DownTo(4)` падает на
|
||||
> `users_email_key`, SQLSTATE 23505). Найдено ревью P2, перепроверено зоной, принято как цена правила.
|
||||
> `users_email_key`, SQLSTATE 23505). Принято как цена правила.
|
||||
9. **Ключ личности — `(provider, subject)`; почта не ключ.** `users.email` стала NULLABLE и БЕЗ
|
||||
уникального индекса; неизвестная пара всегда создаёт НОВЫЙ аккаунт. Разбор и цена решения —
|
||||
в журнале зоны, раздел «Политика коллизии почты».
|
||||
|
|
@ -81,7 +80,7 @@
|
|||
Это первая половина ключа личности. Направить `TM_PLATFORM_OIDC_ISSUER` на другой IdP, оставив
|
||||
имя прежним, — значит сложить `sub` нового провайдера в старое пространство имён, то есть тихо
|
||||
связать чужие аккаунты. Переменная называется здесь, потому что в деплой-примере её не было и
|
||||
оператору нечему было напомнить (найдено ревью P2).
|
||||
оператору нечему было напомнить.
|
||||
11. **Секреты — через `*_FILE`.** `TM_PLATFORM_DSN_FILE` и `TM_PLATFORM_OIDC_CLIENT_SECRET_FILE`
|
||||
читаются раньше одноимённых переменных: переменная окружения видна в `/proc/<pid>/environ` и
|
||||
наследуется каждым ребёнком-`tmctl`. Это же формат `LoadCredential=` systemd (`deploy/`).
|
||||
|
|
@ -156,9 +155,8 @@
|
|||
**7.6.2 выполнено:** сессия создаётся только в колбэке потока, который человек начал явным
|
||||
действием, и провайдер показывает свой экран согласия. Без взаимодействия сессия не появляется.
|
||||
|
||||
**7.3.1 / 7.3.2 — прицел, которого этому разделу не хватало.** 7.1.x требуют ДОКУМЕНТА, и выше
|
||||
он написан; сами механизмы требуют другие две строки, и на них до 08.08 не ссылался ни один
|
||||
наш док. Дословно (ASVS 5.0 V7, обе — уровень **2**): 7.3.1 — «Verify that there is an inactivity
|
||||
**7.3.1 / 7.3.2 — про МЕХАНИЗМ, а не про документ, которого требуют 7.1.x.** Дословно
|
||||
(ASVS 5.0 V7, обе — уровень **2**): 7.3.1 — «Verify that there is an inactivity
|
||||
timeout such that re-authentication is enforced according to risk analysis and documented
|
||||
security decisions»; 7.3.2 — то же про «absolute maximum session lifetime». ⚠ Читать точно: они
|
||||
требуют не КОНКРЕТНОЙ величины, а того, чтобы механизм СУЩЕСТВОВАЛ и принуждал к повторной
|
||||
|
|
@ -236,7 +234,7 @@
|
|||
|
||||
24. **Метрики — `prometheus/client_golang` v1.24.1, на ОТДЕЛЬНОМ слушателе, и это выбор против stdlib.** Норма зоны требует stdlib прежде библиотеки (`ENGINEERING_STANDARDS` §1), поэтому первым рассмотрен `expvar` — и он этой работы не несёт: нет лейблов (⇒ «запросы по маршруту и коду» не выражаются вовсе), нет гистограмм (⇒ на вопрос о задержке остаётся среднее — единственная статистика, которая прячет хвост), а его JSON не читает ни один скрейпер без переводчика. Сэкономил бы он зависимость, а стоил бы написания недостающих трёх руками — то есть ровно того самописного пути, который та же норма и запрещает. OpenTelemetry для этого деплоя тяжелее: коллектор процессом, протокол экспорта настройкой, и всё равно scrape-эндпоинт на конце. Пин сверен живьём 11.08 (`proxy.golang.org/@latest`), релиз 24.07.2026.
|
||||
|
||||
**Внешний эталон оси (половина PD-115):** практики именования Prometheus (базовые единицы — секунды и байты; `_total` у счётчиков; единица не в лейбле) плюс «четыре золотых сигнала» на вопрос «что мерить». Пин на форму — `metrics.TestTheRunnersStateIsExposedWithItsUnits`, он же ловит единицу в имени.
|
||||
**Внешний эталон оси (половина PD-115):** практики именования Prometheus плюс «четыре золотых сигнала» на вопрос «что мерить» — обе нормой в `ENGINEERING_STANDARDS.md` §2. Пин на форму — `metrics.TestTheRunnersStateIsExposedWithItsUnits`, он же ловит единицу в имени.
|
||||
|
||||
**Кардинальность:** лейбл несёт ПАТТЕРН маршрута, никогда путь — сырой путь это библиотека пользователя в индексе оператора (PD-3) и неограниченное число рядов; неразобранный запрос сворачивается в один ряд `(unmatched)`, потому что там лейбл выбирает не сервер. Пин — `TestRequestsAreCountedByRoutePatternAndNeverByPath`.
|
||||
|
||||
|
|
@ -284,8 +282,7 @@
|
|||
СТАРТУЕТ (отказ конфигурации, не предупреждение) · личность берётся из конфигурации, а не из
|
||||
запроса, поэтому «войти кем-то другим» через него нельзя в принципе · провайдер называется `dev`,
|
||||
и в это пространство ключа `(provider, subject)` не может попасть ни один настоящий издатель.
|
||||
Плюс WARN на буте и на каждый выданный сеанс. Разбор — доккоммент `internal/login/dev.go`;
|
||||
security-ось ревью атакует именно этот список.
|
||||
Плюс WARN на буте и на каждый выданный сеанс. Разбор — доккоммент `internal/login/dev.go`.
|
||||
|
||||
⚠ **Второй гейт того же класса, из приёмки P6 (PD-228):** дев-окружение экспортирует ДВЕ опасные
|
||||
переменные, и отказ по одной ловил «стенд уехал в прод» наполовину. Поэтому
|
||||
|
|
@ -315,7 +312,7 @@
|
|||
ничего не мешает. Цена забывания названа замером: книга уезжает `finished_at`-нутой и НЕ
|
||||
должной, очередь дрейна ключуется этой колонкой и больше на закрытую книгу не смотрит — то есть
|
||||
прогон, за который пользователь заплатил, не показывает свой текст никогда. Две из трёх и
|
||||
забыли; поймало ревью, пин — `pgstore.TestEveryEndingOfARunLeavesTheBookOwingASurface`, по
|
||||
забыли; пин — `pgstore.TestEveryEndingOfARunLeavesTheBookOwingASurface`, по
|
||||
таблице из трёх концовок.
|
||||
|
||||
34. **`books.read_model_owed_at` — это СРОК, а не момент возникновения.** Тот, кто берётся платить
|
||||
|
|
@ -393,7 +390,7 @@
|
|||
| `tmctl export --json --pairs` | движок (`pipeline/export.go`) | — (одноразовая выдача на вызов) | `internal/runner/engine.go` `ExportArgs`/`Export` → `ingest.DecodeExport` → `internal/readmodel` | ✅ ⚠ **Единственный канал, несущий ТЕКСТ пары** — исходник и перевод; манифест несёт только структуру |
|
||||
| `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`; расчёт денег | ✅ форму P8-FIX не менял; изменил лишь то, СКОЛЬКО раз его зовут при неудаче (бэкофф отсрочки) |
|
||||
| `tmctl status --json` | движок | — | `internal/runner/engine.go` → `ingest.StatusReport`; расчёт денег | ✅ ⚠ при неудаче зовётся не подряд, а с бэкоффом отсрочки |
|
||||
| `book.yaml` | оператор (шаблон) + **платформа ОДИН раз** (`internal/books/render.go`, `O_EXCL`) | — | платформа пишет пять фиксированных ключей: `book_id · title · source_lang · target_lang · source_file` | ⚠ **декодер движка СТРОГИЙ** (`config/book.go`, `dec.KnownFields(true)`): незнакомый ключ — жёсткая ошибка, не предупреждение. Значит любое расширение набора ключей платформой это РАТИФИКАЦИЯ, а не правка: сборка движка, которая ключа ещё или уже не знает, перестанет грузить КАЖДУЮ новую книгу. Прямо относится к развилке 199(а) |
|
||||
|
||||
⚠ **Два следствия, которые стоит держать в голове при любой работе со швом.** Первое: относительные
|
||||
|
|
@ -438,8 +435,8 @@ sed -e 's#^pipeline: ../configs/#pipeline: <repo>/backend/configs/#' \
|
|||
system_messages not found in type config.CapabilitiesConfig`. Диагноз стоит времени именно потому,
|
||||
что выглядит как дефект зоны: падает платформенный тест, а лжёт бинарь движка, собранный до того,
|
||||
как в конфиг движка приехало поле. То же правило и той же причины — для КОНФИГОВ стенда, если они
|
||||
скопированы рядом с бинарём: пак P12 нашёл стендовую копию `backend/configs` без `langpacks/ru`,
|
||||
появившегося позже.
|
||||
скопированы рядом с бинарём: стендовая копия `backend/configs` без появившегося позже
|
||||
`langpacks/ru` (`PD-432`).
|
||||
|
||||
⛔ **Команда-проверка четвёртого условия СНЯТА и не подлежит восстановлению без нового диагноза.**
|
||||
Прежняя редакция предлагала одну строку: `cut -d: -f3 /proc/self/cgroup` не должен давать
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ Postgres 18.4 без root (`~/.local/pgsql`, сокет `/tmp`, порт 55433)
|
|||
`127.0.0.1:8099`, метрики `127.0.0.1:9464` · по своей копии дерева, своей базе и своей паре портов
|
||||
на каждого из восьми субагентов.
|
||||
|
||||
Базовая линия батареи на дереве репозитория, ТРИ гейта выполнены (DSN · пара
|
||||
`TM_PLATFORM_TEST_ENGINE_BIN`+`_BOOK_TEMPLATE` · достижимый пользовательский менеджер systemd):
|
||||
**18 пакетов, EXIT=0, скипов 0, линтер 0 issues** — `battery-final.log`.
|
||||
Базовая линия снята на дереве репозитория при ТРЁХ выполненных гейтах (DSN · пара
|
||||
`TM_PLATFORM_TEST_ENGINE_BIN`+`_BOOK_TEMPLATE` · достижимый менеджер systemd пользователя);
|
||||
числа прогона — строка `battery-final.log` таблицы ниже.
|
||||
|
||||
## ⚠ Два правила, без которых мутации читаются неверно
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
> читает этот журнал при каждом лендинге зоны (свип «решений владельца» — норма D39.99 п.4).
|
||||
|
||||
|
||||
|
||||
## ПИНГ оркестратора №21 → зоне (31.08, СРОЧНО): ваша копия полосы отказов обещает то, что движок ОТОЗВАЛ
|
||||
|
||||
Правку вносит ЗОНА. Пишу пингом, а не правкой, потому что `platform/` не моя зона — но откладывать
|
||||
|
|
@ -950,12 +949,12 @@ HTTP-старт → настоящий спавн → настоящий вых
|
|||
книга, чтобы исполнялся фильтр `r.book_id`). Раунд первый доказал, что `min` исполняется как ВЫБОР,
|
||||
но не что исполняется книжный фильтр. Пропуск, а не подтверждение.
|
||||
|
||||
⚠ **ДВА флейка под нагрузкой, оба НЕ мой дифф.** При трёх параллельных батареях краснеют в ЧИСТЫХ
|
||||
копиях `TestARunIsBoundedByItsOwnCgroup` (4 раза из 15) и
|
||||
`TestAClaimThatLostARaceToAReleaseIsRetriedAndNotAnError` (2). В серийных прогонах — ни разу, ни у
|
||||
меня, ни у оркестратора. Оба меряют ресурс, общий для копий на машине. Именно поэтому вердикт —
|
||||
ДЕЛЬТА множеств, а не код выхода: флейк, попавший в чистую копию, вычитается из посаженной. Заведено
|
||||
строкой `PD-420`.
|
||||
⚠ **ДВА флейка под нагрузкой, оба НЕ мой дифф** — при трёх параллельных батареях краснеют в ЧИСТЫХ
|
||||
копиях `TestARunIsBoundedByItsOwnCgroup` (**4 раза из 15**; строка `PD-423` несёт другой замер — красноту
|
||||
в ИЗОЛЯЦИИ и её причину) и `TestAClaimThatLostARaceToAReleaseIsRetriedAndNotAnError` (**2 из 15**;
|
||||
строка `PD-420`). В серийных прогонах — ни разу, ни у меня, ни у оркестратора: оба меряют ресурс, общий
|
||||
для копий на машине. Отсюда и вердикт кампании — **ДЕЛЬТА множеств, а не код выхода**: флейк, попавший в
|
||||
чистую копию, вычитается из посаженной.
|
||||
|
||||
⚠ **Одна посадка научила чинить не код, а ПИН.** `r_nocheck` в первом прогоне не дала ни одного
|
||||
названного красного — она ПОВЕСИЛА пакет `internal/httpapi` на десятиминутном таймауте Go, потому что
|
||||
|
|
@ -1005,18 +1004,13 @@ HTTP-старт → настоящий спавн → настоящий вых
|
|||
9. **Живые сценарии сняты на ДЕВ-профиле** (`INSECURE_COOKIES`, `DEV_LOGIN`), то есть на кукe без
|
||||
`__Host-` и без OIDC. Механизм отзыва от этого не зависит — он в сторе, — но «проверено на
|
||||
проде-подобном профиле» я сказать не могу.
|
||||
10. **⛔ Я убила чужие процессы.** Гася свою мутационную кампанию, применила `pkill -f 'go test'` и
|
||||
`pkill -9 -f '/exe/'` — на общей машине это шаблон «все, кто сейчас работает». Попала по шести
|
||||
ревью-агентам сессии `textmachine-e4` в окне **01:36:20–01:37:45**, включая линзу, которая судит
|
||||
покрытие посадкой мутаций: для неё убитый прогон читается как «мутация выжила», то есть я могла
|
||||
подсунуть ей ложную НАХОДКУ. Сообщила ей сама, с точными границами; она пере-прогоняет. Своё
|
||||
поведение изменила: PID заданий пишутся в файл, гашу построчно. Это моя ошибка целиком, и она
|
||||
стоила чужого времени.
|
||||
11. **Кампания посадок ДОШЛА до конца уже после первой редакции этого отчёта**, и её таблица — то
|
||||
место, где отчёт дольше всего был неполон. Числа батареи, на которые ссылается таблица сдачи,
|
||||
сняты в 02:04, а дерево правилось после (комментарии, дедлайн в тесте, текст миграции и её
|
||||
контрольная сумма) — поэтому финальный прогон снят заново и таблица указывает на него.
|
||||
12. **Не проверено вообще:** поведение при нескольких одновременных потоках одного пользователя под
|
||||
10. **⛔ Я убила чужие процессы** `pkill -f 'go test'` / `pkill -9 -f '/exe/'`, гася свою мутационную
|
||||
кампанию: шесть ревью-агентов сессии `textmachine-e4` в окне **01:36:20–01:37:45**, включая линзу
|
||||
покрытия посадками — для неё убитый прогон читается как «мутация выжила», то есть я могла
|
||||
подсунуть ей ложную НАХОДКУ. Сообщила ей сама с точными границами, она пере-прогнала; PID заданий
|
||||
теперь пишутся в файл и гасятся построчно. Общий урок — в списке уроков `docs/PROGRESS.md` (греп
|
||||
«`pkill` по имени процесса») и в `STACK_DECISIONS` (греп «Демон нельзя убивать»).
|
||||
11. **Не проверено вообще:** поведение при нескольких одновременных потоках одного пользователя под
|
||||
отзывом (логически покрыто — проверка у каждого своя, — но живьём не снято) · `logout-all` на
|
||||
дев-профиле (`PD-379` мимоходом сообщает про 404; я его не пере-проверяла, это строка группы
|
||||
`PD-380…383`, которую пак не берёт) · поведение при недоступном Postgres в момент проверки
|
||||
|
|
@ -1105,6 +1099,13 @@ FAIL/DATA RACE — 0; `golangci-lint` → **0 issues**;
|
|||
деплоя (byte no-op проставит факт по already_applied-ветке).
|
||||
- Консент-гейт движка живьём деньгами по-прежнему не пробит ($0-цены; кандидат строки 202) — из
|
||||
прежнего Obstacle, не изменилось.
|
||||
- ⚠ **Для оркестратора — находка опровергателя P10, носителя ни в регистре, ни в бэклоге у неё нет:**
|
||||
якоря §2.12 компаньона контракта (`docs/architecture/14-api-contract/README.md`, греп
|
||||
`pipeline/status.go`) ПРОТУХЛИ — волновая машинерия **D39.122** увезла деньги в `ChapterPassport` и
|
||||
`StatusReport` (греп `type ChapterPassport` в `backend/internal/pipeline/status.go`). Там же довод
|
||||
опровергателя, ради которого правка §2.12 ОБЯЗАТЕЛЬНА: два денежных поля из пяти (`Spend`,
|
||||
`Reserved`) уже легально пересекают шов в заленженном аллоулисте, поэтому безусловное «§2.12
|
||||
запрещает шов» делало бы их нарушениями задним числом.
|
||||
|
||||
## ⚠ ПОЧЕМУ ФОРМА P10 СО СМЕТОЙ БЫЛА ОТВЕРГНУТА — 42 находки/6 линз широкого самопрохода, три корня валят ФОРМУ (сессия платформы, 28.08)
|
||||
|
||||
|
|
@ -1204,94 +1205,36 @@ FAIL/DATA RACE — 0; `golangci-lint` → **0 issues**;
|
|||
|
||||
## ВОРКФЛОУ-РЕВЬЮ ДЕРЕВА P9 ОТРАБОТАНО — 16 линз, оба отложенных MAJOR подтверждены замером, сводка находок для оркестратора (сессия платформы, 28.08)
|
||||
|
||||
Заказ владельца (релей 28.08): адверсариальная вычитка дерева воркфлоу-оркестрацией — полоса ×4,
|
||||
дверь ×3, миграция, раскладка кодов (`wf_cac14b2f-e84`) + гонки данных по 4 траекториям владельца,
|
||||
баг-хант нового кода, стоимость per-request (`wf_155de7c3-bb4`). Раскладка моделей — по слову
|
||||
владельца: 1×Fable на воркфлоу (самая тяжёлая линза), остальным явный opus/sonnet. 16/16 агентов
|
||||
дошли (0 ошибок), ~2.83M токенов. Мандат: «найди, где рассуждение неверно», каждая находка —
|
||||
severity + file:line + траектория с числами; PD-281, PD-401-остаток и пере-нарезка исключены
|
||||
заданием как названные границы. **По находкам НИЧЕГО не чинил — жду слова оркестратора.**
|
||||
Адверсариальная вычитка дерева воркфлоу-оркестрацией (заказ владельца, релей 28.08; журналы
|
||||
`wf_cac14b2f-e84` и `wf_155de7c3-bb4` вне репо): 16 линз — полоса ×4, дверь ×3, миграция, раскладка
|
||||
кодов, гонки данных по 4 траекториям владельца, баг-хант, стоимость per-request; раскладка моделей по
|
||||
слову владельца (1×Fable на самую тяжёлую линзу, остальным явный opus/sonnet); 16/16 агентов дошли,
|
||||
0 ошибок, ~2.83M токенов. Мандат: «найди, где рассуждение неверно». ⚠ Пометка «подтверждено прогоном»
|
||||
в траекториях — исполнение АГЕНТОВ, не сессии; сессия пере-исполнила два клейма своего кода и одним
|
||||
своим EXPLAIN — ось стоимости. **Лечение — таблица F1–F13 секцией выше, приёмка — D39.162.**
|
||||
|
||||
Дисциплина честности: пометка «подтверждено прогоном» в траекториях — исполнение АГЕНТОВ (их
|
||||
overlay-тесты и /tmp-замеры в копиях дерева), не моё; я пере-исполнил чтением два клейма своего кода
|
||||
(Р4, Р5 ниже — оба подтвердились) и одним своим EXPLAIN-замером ось стоимости (сошлось, ниже).
|
||||
Полные результаты с траекториями сохранены вне репо: журналы воркфлоу в каталоге сессии
|
||||
(`subagents/workflows/wf_*/journal.jsonl`).
|
||||
**Куда уехала каждая находка** (33 штуки; здесь остаётся ровно то, чего нет ни в F-таблице, ни в
|
||||
строках). MAJOR(а) «1 МиБ на двух документах» → **F1**, MAJOR(б) «мьютекс без контекста» → **F2**.
|
||||
Свой код P9: Р1 → F3 · Р2 → F4 (и dispute про READ COMMITTED растворён — стейтмент один) · Р3 →
|
||||
**`PD-410`** (архитектурное, отдельным паком) · **Р4 ОПРОВЕРГНУТА исполнением** — несогласие 1 выше и
|
||||
D39.162, держать её как находку нельзя · Р5 → F5 · Р6 → F6-F8 и **`PD-407`** · Р7 → F9 и **`PD-409`**
|
||||
· Р8 → редакция **`PD-400.2`**. Наследие платформы: Н1 → F12 и **`PD-402`** · Н2 → **`PD-403`** ·
|
||||
Н3 → **`PD-404`** · Н4 → **`PD-405`**, мёртвые колонки — **`PD-411`** · Н5 → F11 и **`PD-406`**.
|
||||
Движковые: Д1 → строка **228** единого бэклога (пере-сформулирована бэкенд-сессией и принята), Д4 →
|
||||
строка **227** (якорь находки был неверен и исправлен приёмкой), Д5 → F10. Ось стоимости: карточка в
|
||||
5 сканов → **`PD-412`**, `emitProgress` под блокировкой → **`PD-413`** (оба несут замеры целиком);
|
||||
строку **186** приёмка закрыть ОТКАЗАЛАСЬ — замер подтверждает шаги 1-2, шаги 3-5 живы (D39.162), и
|
||||
сам замер стоит в строке.
|
||||
|
||||
### Два отложенных MAJOR ревьюера приёмки — оба ПОДТВЕРЖДЕНЫ, чинить
|
||||
- cosmetic: `MkdirAll(StateDir)` на каждый вызов двери — место одному разу в конструкторе Service.
|
||||
|
||||
- **MAJOR(а) «1 МиБ на двух документах» — ПОДТВЕРЖДЁН тремя агентами с независимыми замерами.**
|
||||
Механизм: `json.Marshal` в `ingest.EncodeDecisions` HTML-экранирует `&`/`<`/`>` (1 байт → 6) и
|
||||
меняет конверт (+~25 Б), так что тело под проводным капом 1 МиБ рендерится в документ НАД
|
||||
движковым капом 1 МиБ. Замер агента: 5000 declines с `note` из `&`×160 → тело 1 030 050 Б →
|
||||
рендер 5 030 075 Б → движковый exit 14 → **409 `bank_corrections_refused` «split it» вместо
|
||||
канонного 413**. Полоса законных тел широкая (~550 КБ плотного спецсимволами тела уже
|
||||
перескакивает), а существующий пин `TestAnOversizedCorrectionDocumentAnswers413` дыру не ловит —
|
||||
набит кириллицей, которая НЕ экранируется. ⚠ Конфликт линз: два агента первого воркфлоу положили
|
||||
эту ось в «не опровергнуто», рассуждая от omitempty («рендер меньше тела») и НЕ меряя; замер двух
|
||||
других бьёт рассуждение (один из «опровергателей» сам оговорил условие плотности `&<>`).
|
||||
Бонус-cosmetic туда же: скраб пути (`runs/bank.go:146`) на этом отказе даёт задвоенную фразу «the
|
||||
decision document the decision document» и пропускает на провод движковый префикс `pipeline:`.
|
||||
- **MAJOR(б) «мьютекс без контекста, бюджет после захвата» — ПОДТВЕРЖДЁН** (`runs/bank.go:94` vs
|
||||
`:126`, `lockBook` без ctx `:270-291`): K одинаковых ретраев дают очередь K×60 с; отменённый
|
||||
клиентом запрос ЖДЁТ в очереди и, дождавшись, делает работу (чтение книги, temp-файл) за мёртвый
|
||||
запрос; в ту же очередь встают Start и Resume — заявление «ждать один вызов движка»
|
||||
(`runs.go:242-246`) держится только для одного ожидающего. Дёшево лечится ожиданием с
|
||||
наблюдением ctx (канал вместо `sync.Mutex`).
|
||||
|
||||
### Находки в МОЁМ коде P9 (сверх двух MAJOR; лечение — после слова оркестратора)
|
||||
**Две движковые находки БЕЗ носителя — это и есть живой остаток секции** (чужая зона, лечить не мне;
|
||||
пинг оркестратору):
|
||||
|
||||
| # | Вес | Что | Где |
|
||||
|---|---|---|---|
|
||||
| Р1 | breaks | **Окно стопа подписи**: `bank_stop` ставит `awaiting_bank` БЕЗ `finished_at`, а `HasLiveRun` читает `finished_at is null` → до ближайшего свипа (15 с штатно; до 30 мин на бэкоффе отсрочек) дверь отвечает **409 `run_in_flight` на экран подписи, который платформа сама только что объявила**; соседняя кнопка Resume в том же окне отказывает ДРУГИМ словом («being settled»). Мой довод в `runs/bank.go:110-112` («its run is closed») прямо противоречит `reconcile.go:1122`; состояние строится собственным тестом дерева (`control_test.go:598-603`). Нашли НЕЗАВИСИМО 3 агента | `runs/bank.go:109` |
|
||||
| Р2 | breaks | **Миграция 00026 для прогонов в полёте**: пере-снятие баз посреди прогона, уже сделавшего p_e правок, оставляет его финал на `runDone−runTotal = −2·p_e` — полностью доделанный прогон закрывается, например, 5/7 навсегда. Двух-базовая схема корректна ровно потому, что базы берутся ДО работы прогона; миграция — единственное место, берущее их ПОСЛЕ. + dispute: READ COMMITTED между двумя UPDATE допускает двойное касание строки при конкурентном финише (при штатном деплое одной реплики окно закрыто остановкой старого демона — вес мал) | `migrations/00026:20-27` |
|
||||
| Р3 | breaks | **`draftWork` не моделирует порядок волн движка**: continuation над недочерновленной книгой (`draft_before ≥ chapters_before+C`, напр. прогон-предшественник упёрся в потолок на черновике) даёт `draftWork=0`, движок продолжает ЧЕРНОВУЮ волну — полоса 0/C весь прогон, stage «editing» на прогоне, который только черновит. Зеркало дефекта, который draftWork чинил | `readmodel.go:460` |
|
||||
| Р4 | breaks | **`limitedBuffer` НЕ убивает чайлда** при переполнении stdout — вопреки своему комментарию (контраст: `drain()` в `engine.go:262-272` при том же переполнении явно зовёт `Process.Kill`); чайлд виснет на записи в полный пайп до чужого дедлайна (~70 с под пер-книжным мьютексом), без дедлайна — навсегда. Подтвердил чтением. + dispute: stderr — НЕограниченный `bytes.Buffer` (не-тот бинарь по сконфигурированному пути может раздуть демона до OOM; читается всё равно только первая строка) | `runner/bankapply.go:98-111` |
|
||||
| Р5 | breaks | **Presence-vs-null дыра валидации**: только окна (`nullableInt`) различают явный `null` от отсутствия; `src/sense/dst/kind/id` — голые `*string`, так что `{"id":"X","src":null}` и decline с `"dst":null,"kind":null` проходят гейты, которые канонный `oneOf`/`not-required` велит отбивать 400. Подтвердил чтением (сам вводил `nullableInt` только для окон). Порчи данных нет — лишнее значение не читается; дыра контрактная | `httpapi/bank.go:48-51, 221-224, 251-259` |
|
||||
| Р6 | dispute | **Раскладка на ветке SIGKILL**: `ctx.Err()`-ветка (`runs/bank.go:132`) достижима ТОЛЬКО когда SIGTERM не отработал (процесс убит по `WaitDelay`) — а это единственный путь, оставляющий полу-приземлённую пару БЕЗ отчёта, т.е. адресат `bank_corrections_incomplete`, отвечаемый общим 503; мой комментарий описывает соседнюю ветку. + `bankStopGrace` 10 с КОРОЧЕ самого длинного непрерываемого участка движка (11.3 с на документе-максимуме) — грейс истекает до фазы записи. + `runBudget` — операторская ручка свипа: понижение (к чему подталкивают комментарии) делает легальный документ-максимум навсегда неприменимым (вечный 503 вместо «split it») | `runs/bank.go:132`, `runner/bankapply.go:40` |
|
||||
| Р7 | cosmetic | Утечка `bank-corrections-*.json` в StateDir при нечистой смерти демона (SIGKILL/OOM/обрыв грейса деплоя): cleanup только на defer, никто каталог не подметает; до 1 МиБ пользовательских решений на файл, бессрочно | `runs/bank.go:250` |
|
||||
| Р8 | — | **Формулировка PD-400.2 (accepted-risk) требует правки**: на мульти-реплике класс 12 может быть НАСТОЯЩИМ многочасовым прогоном соседней реплики (её Start видит свой мьютекс и чистый `runs`) — слово 503 «transient holder» и мой лог тогда ЛОЖНЫ, канонно верное слово — 409 `run_in_flight`. «Ни денег, ни порчи» держится; «ни ложного слова» — НЕТ. Риск остаётся принятым (v1 = одна реплика), но акт лендинга должен описать его честно | `runs/bank.go:27-39, 182-189` |
|
||||
|
||||
### Находки в НАСЛЕДИИ платформы, вскрытые ревью (не код P9; полоса и дверь на них стоят)
|
||||
|
||||
| # | Вес | Что | Где |
|
||||
|---|---|---|---|
|
||||
| Н1 | breaks | **Resume НЕ-последнего прогона** (Resume пропускает любой `stopped` без проверки последнести; `lastRun` = `started_at desc`; RestartRun не трогает `started_at`): (i) полоса возобновлённого открывается на ЧУЖОЙ работе — 2/2 при нуле своих пассов (клэмп режет только >1, не двойной счёт до 1); (ii) карточка, кадр статуса и КАЖДЫЙ progress-кадр живого прогона считаются по ЧУЖОМУ финишировавшему — «ready» и замороженный бар, пока живой прогон тратит деньги. 4 агента, overlay-прогоны | `reconcile.go:1216`, `readmodel.go:487`, `runs.go:843` |
|
||||
| Н2 | breaks | **`edit_wave` sticky-true × оператор убрал редактора**: прогон делает 100% купленного и закрывается ready на 50% со stage «editing»; `chaptersDone` (edit-колонка) мёрзнет → `ChaptersLeft` не падает → **шкала повторно продаёт уже переведённые главы** (симптом PD-202, записанной fixed); follow-up прогон над этой покупкой читается 0/C с первой секунды. Обе фразы комментария `sink.go:406-409` о собственном коде ложны | `sink.go:415`, `readmodel.go:388` |
|
||||
| Н3 | breaks | **Флип false→true уводит `Book.chapters_done` НАЗАД** (finishedUnits через ЖИВОЙ флаг): 40/100 → 0/100 одной транзакцией при росте ревизии — клиент обязан отрисовать спад; `ChaptersLeft` раздувается обратно → предлагает купить купленное. PD-316-класс на непокрытом триггере (пин ловит только admission). Крайний случай: книга, полностью начерченная под false, НЕдочитываема редактором никаким действием API (ChaptersLeft=0 → прогон не допускается → флип недостижим) | `sink.go:415`, `readmodel.go:388`, `books.go:981-983` |
|
||||
| Н4 | breaks | **Книга без материализованного дерева**: полоса (вся из `chapters`) читает 0/total весь прогон при исправно доезжающих progress-событиях; комментарий `sink.go:219` («счётчики придут из progress-события») ложен уже в HEAD. + dispute: `runs.draft_done/draft_total/edit_done/edit_total` — 2 писателя, 0 читателей (PD-314-класс, лишняя запись на каждом событии под блокировкой книги; обоснование `greatest()` на `sink.go:444-446` защищает несуществующую полосу) | `sink.go:219, 137, 447` |
|
||||
| Н5 | breaks | **SSE `hello` при переподключении несёт `id = state.Position` вместо предъявленного `last`** — WHATWG-клиент фиксирует новый Last-Event-ID ДО получения догона; обрыв сразу после hello теряет кадры навсегда (включая `note`, которые контракт запрещает терять) и обезоруживает дельта-ремонт (`revision` из hello выше потерянных строк). Лечение — одна строка: при resuming слать `last`. Код рядом (`stream.go:122-126`) сам знает правильное значение | `stream.go:99` |
|
||||
|
||||
### Находки зоны ДВИЖКА (чужая зона — пинг оркестратору, мне не лечить)
|
||||
|
||||
| # | Вес | Что | Где |
|
||||
|---|---|---|---|
|
||||
| Д1 | breaks | **Decline не энтити-широк на обратном пути**: отклонённая сущность возвращается в карту/auto-bank/редактору через свой АЛИАС (`reverseSectionTerms` фильтрует по одному ключу, а `mined_rejects` получает только `src`), и память предъявления глушит стоп об этом НАВСЕГДА — нарушен собственный контракт «a declined term never re-enters» (`miner_emit.go:59-63`); approve симметричной утечки не имеет (кластер через строку банка) | `mining.go:548`, `membank/decisions.go:598` |
|
||||
| Д2 | breaks | **Неидемпотентный decline** поверхности подписанного сида, имеющей строку в дельте: первый вызов ПРИНЯТ, повтор ТОГО ЖЕ документа — 409 (гейт `decisions.go:374` судит ДО-состояние, которое свёртка сама стирает); при классе 15 предписанный ре-сенд отбивается ЦЕЛИКОМ — **обещание сходимости 503-ретрая, на котором стоит синхронная дверь, ломается**. Доказано исполнением через СОБСТВЕННЫЙ оракул репозитория (оракул 4 фаззера); фаззер структурно не достаёт (фикс-книга не пересекает сид с дельтой). Лечение: судить по ПОСТ-состоянию, как соседний `refuseInertDeclines` | `membank/decisions.go:374` |
|
||||
| Д3 | breaks | **Потеря/порча маркера выхода после стопа** (совместная с платформой): рестарт/ретрай проходит границу банка НАСКВОЗЬ — память предъявления покрывает карту, движок «continuing» одним WARN себе в журнал — оплаченный `verify_bank` стоп исчезает молча и навсегда (память append-only). Гард `LiftBankStop` (`reconcile.go:1122-1126`) писан против движка ДО памяти v16 и этот путь не держит | `reconcile.go:424`, `mining.go:216-227` |
|
||||
| Д4 | dispute | **`signature` в квитанции двери** считается от карты, которую переписывает ЛЮБАЯ граница майнинга (запись карты на `mining.go:191` — ВЫШЕ решения о стопе): `signature != null` не означает состоявшегося стопа; `surfaces`/`undecided` дрейфуют между двумя вызовами владельца; `undecided: 0` достижим при непредъявленных решениях (кап top-200 вытесняет). У карты ЕСТЬ идентификатор (`signaturemap.go:25-29`) — шов его не читает | `pipeline/bankdecisions.go:549-598` |
|
||||
| Д5 | cosmetic | `spawn.go:159-165` (платформа, но про движок) цитирует упразднённый D39.144-контракт стопа («halts whenever undecided terms remain») — при памяти v16 фактически ложно и питает дырявый гард Д3 | `spawn.go:159-165` |
|
||||
|
||||
### Ось «стоимость» — мои живые замеры + вердикт агента (сошлись)
|
||||
|
||||
- **Строка 186 единого бэклога ПРОТУХЛА — рекомендация оркестратору: закрыть.** Механизм построен
|
||||
и применён: агент прогрепал ВСЕ call sites `writeJSON` (коллекции, карточка, capabilities, POST
|
||||
двери; SSE корректно НЕ сжимается). Мои замеры на стенде tmp9stand (команды и заголовки — сырьём):
|
||||
банк 437 Б / 7.2 мс холодным, повтор с `If-None-Match` → **304 / 0 Б / 2.6 мс**; юниты главы
|
||||
3008 Б → **740 Б gzip (×4.1)**; gzip ниже порога 1024 Б честно не применяется; resume SSE за
|
||||
головой → 204 / 1.3 мс. Взамен предлагаю ДВЕ новые стоимостные строки (ниже).
|
||||
- **Карточка книги — 5 коррелированных сканов `chapters` на один GET** (3 в runRow: draftChapters
|
||||
×2 + editChapters, +2 в bookColumns: chaptersDone + noteCount), и те же 2 — на КАЖДУЮ строку
|
||||
страницы библиотеки (×100). Эмпирика агента на живом PG 18.4, фикстура в форме миграции 00002,
|
||||
книга 2283 главы: как написано — 1.359 мс; те же числа одним LATERAL `count(*) filter(...)` —
|
||||
0.307 мс (×4.4). Мой контрольный EXPLAIN на реальной схеме tmp9stand: **6 SubPlan-ов в плане,
|
||||
5 исполняются** (ELSE-ветка never executed) — форма подтверждена. PG повторные текстовые
|
||||
вхождения подзапроса НЕ дедуплицирует (доказано side-effect-последовательностью), CASE-ветки
|
||||
честно short-circuit.
|
||||
- **`emitProgress` повторяет 3-скан агрегат по ВСЕЙ книге на каждое progress-событие** (движок шлёт
|
||||
его на каждый разрешённый юнит каждой волны) — ПОД блокировкой строки книги: O(глав × юнитов)
|
||||
вместо O(юнитов), на большой книге — секунды суммарного удержания блокировки за прогон.
|
||||
- cosmetic: `MkdirAll(StateDir)` на каждый вызов двери — место одному разу в конструкторе Service.
|
||||
|
||||
### Чистые оси (проверено — не опровергнуто)
|
||||
|
||||
|
|
@ -1306,27 +1249,6 @@ bankCountsTx; проекции без утечек словаря/путей; bu
|
|||
термов через пересборку банка (id из ключа уникальности); сериализация чеканки кадров и штамповка
|
||||
ревизий чисты; чтения на одном снимке; идемпотентный ключ Start не клинит за очередью мьютекса.
|
||||
|
||||
### Финальная батарея по дереву (после всех приёмочных правок; дерево ревью НЕ меняло)
|
||||
|
||||
`go test ./... -race -count=1 -v` с тремя гейтами → **EXIT=0, 18 пакетов ok, SKIP=0,
|
||||
`^=== RUN` = 781**; DATA RACE — нет. (На сдаче было 775 — рост на приёмочных доборах, число
|
||||
командой.)
|
||||
|
||||
### Вопросы/предложения оркестратору
|
||||
|
||||
1. Диспозиция Р1–Р8: что чиню в этом дереве до лендинга, что строками регистра. Готов завести
|
||||
пакет строк PD-40x по всем группам после твоего вердикта (не завожу до слова — рядом лендинг).
|
||||
2. PD-400.2: скорректировать формулировку accepted-risk по Р8 (снять «ни ложного слова», описать
|
||||
мульти-репличный суб-кейс) ДО акта лендинга.
|
||||
3. Строку 186 закрыть (замер выше), взамен — две стоимостные строки (5-скан карточка ×100 на
|
||||
библиотеку; emitProgress O(глав×юнитов) под блокировкой) куда решишь.
|
||||
4. Д1–Д5 — пинг бэкенду твоим каналом; Д2 ломает обещание, которое МОЯ дверь даёт на проводе
|
||||
(сходимость ретрая 503) — до его лечения в движке слова канона §applyBankCorrections о ретрае
|
||||
верны не для всех документов.
|
||||
5. Н1–Н5 — наследие: Н1 (resume не-последнего) и Н5 (hello id, лечение в одну строку) выглядят
|
||||
дешёвыми и болезненными; Н2/Н3 упираются в продуктовое решение о смене формы конвейера на
|
||||
живой книге.
|
||||
|
||||
## ПАК P9 ОТРАБОТАН — дверь правок банка смонтирована, ключи едут, полоса сквозная; цепь живого прогона ПРОБИТА живьём (сессия платформы, 27.08)
|
||||
|
||||
Дерево передаётся на лендинг. Опись: `git status --short -- platform/` → 31 изменённый + 9 новых
|
||||
|
|
@ -1543,6 +1465,16 @@ mutation-catch (спутать пары «числитель×база»); ко
|
|||
«до 17 раз» в `PD-377`, живая проба `PD-96`, экономика `PD-215`, кардинальность лейблов. Они
|
||||
остаются названными пробелами, а не тихими.
|
||||
|
||||
**Числа сдачи зоны — провенанс, другого носителя у них нет** (ни в `D39.159`, ни в
|
||||
`platform/docs/p8-review/`, где лежит только сырьё): посадок мутаций **16** своих, все воспроизводимы
|
||||
(`plant.py` знает `M1`–`M16` поимённо, логи рядом) — **7 поймано, 9 выжило**; агентских **42** по их
|
||||
таблицам (`agent-mutations.md`) — **21/21**. Уникальных переживших инвариантов **15**, каждый несёт
|
||||
строку регистра.
|
||||
⚠ **Метод-урок той же сдачи, и место ему не здесь:** обрезанный по ширине вывод грепа читается как
|
||||
ОТСУТСТВИЕ совпадения — так зона объявила «грепа в промте ноль» на строке, которую греп нашёл. Дом
|
||||
урока — `platform/docs/ENGINEERING_STANDARDS.md` (дисциплина доказательства грепом); перенос за тем,
|
||||
кто правит тот файл.
|
||||
|
||||
**Об ошибках, которые вы назвали сами** (пять, от овер-атрибуции `PLATFORM_DIRECTION` до неполной
|
||||
описи): называть их — правильно, и опись, недобравшая три файла из пяти, действительно стоила бы
|
||||
лендеру правки НОРМЫ. Продолжайте так же.
|
||||
|
|
@ -1746,33 +1678,47 @@ D39.179 п.1 ратифицировал: DeepSeek пере-пинен под ц
|
|||
- **Потолок (строка 145): `tmctl translate|redrive --ceiling-usd <usd>` — это КНИЖНЫЙ ПОТОЛОК В СИЛЕ, не бюджет прогона** (ратифицировано D39.122): леджер сравнивает значение с накопленным committed+reserved книги. Пересчёт пользовательского «прирост в главах» (D39.110) в абсолют — ОБЯЗАННОСТЬ платформы. **⚠ АМЕНДИРОВАНО D39.123 (PD-158): формула = `committed_usd + прирост×оценка`, БЕЗ reserved** — read-only `status` показывает leftover-reserved, который `store.Open` зануляет до первой судимой резервации; включение переплачивало бы запасом сверх холда (исполнено обеими формулами против гейта). `reserved_usd` читается обязательным полем как гард присутствия и улика leftover. Значение ниже уже потраченного откажет первой же резервации. День-потолок не перекрывается. Опция по желанию зоны: движок готов провести `--ceiling-usd` и в `status`, чтобы мониторинг видел действующий потолок capped-прогона (сегодня status показывает книжный) — скажите, заведём строку.
|
||||
|
||||
|
||||
|
||||
## Пинг оркестратора №16 — 09.08.2026 (форма DEFECT_REGISTER, строка 167)
|
||||
|
||||
Реестр дефектов дорос до 171 строки одной таблицей; докс-аудит D39.125 (worksheet `docs/archive/reports/DOC_AUDIT_INVENTORY_2026-08-09.md`, жалоба «зонная изоляция прячет половины тем») просит форму: разложить таблицу на СЕКЦИИ (open по весу · accepted-risk · fixed по эрам паков), сохранив построчную форму `| PD-N | … |` — `docs/scripts/counts.py` ключуется формой строки, не позицией, секции его не ломают. Заведите П-строкой в свой BACKLOG, исполнение — попутно следующим паком, НЕ срочно. Вопросы — через владельца.
|
||||
ИСПОЛНЕН: реестр разложен на секции (открытые по весу · принятый риск · закрытые по эрам паков) при
|
||||
сохранённой построчной форме `| PD-N | … |`, на которой ключуется `docs/scripts/counts.py`. Заказ и
|
||||
его причина — D39.125.
|
||||
|
||||
## Пинг оркестратора №17 — 15.08.2026 (migrate принят, D39.134 — движковая половина самолечения ФИНАЛЬНА)
|
||||
|
||||
Бэкенд-пак `tmctl migrate` принят и заленден (`d55edd4`): exit 13 = `schema_mismatch` финализирован, токен `schema_mismatch found=N expected=M` на stderr стабилен, write-путь тоже отказывает БД новее бинаря. Ваш PD-201 (самолечение «поймал 13 → migrate → повтор») теперь можно строить — движок больше не сдвинется под ногами; строку реестра, ссылающуюся на «движкового migrate ещё нет», обновите. Хвосты вашей зоны, найденные приёмкой (вход промта P7, исполнение попутно): (1) `deploy/README.md:131` цитирует МЁРТВЫЙ текст ошибки схемы («schema vN … expects vM») — движок его больше не печатает; (2) та же мёртвая цитата и уплывший якорь `store.go:135-143` в строке П-1 вашего BACKLOG; (3) комментарий-образец в `tmplatformctl` (`runs.go:45`) зовёт голый `tmctl` из PATH — копипаст воспроизводит тихий no-op СТАРЫМ бинарём при живом деадлоке, ваш же README:139-141 требует версионированный путь. Вопросы — через владельца.
|
||||
Бэкенд-пак `tmctl migrate` принят и заленден (`d55edd4`): exit 13 = `schema_mismatch` финализирован,
|
||||
токен `schema_mismatch found=N expected=M` на stderr стабилен, write-путь тоже отказывает БД новее
|
||||
бинаря — то есть движок под ногами больше не сдвинется, и `PD-201` («поймал 13 → migrate → повтор»)
|
||||
можно строить; строка регистра открыта и несёт предмет. Три хвоста зоны, найденные той же приёмкой
|
||||
(мёртвая цитата текста ошибки схемы в деплой-рантбуке и в строке П-1 бэклога; образец в
|
||||
`tmplatformctl`, зовущий голый `tmctl` из PATH), с тех пор закрыты правками — рантбук цитату сам
|
||||
называет мёртвой, образец несёт версионированный путь.
|
||||
|
||||
## Пинг оркестратора №17 (второй) — 15.08.2026 (S4 принят, контракт 0.2.3 в каноне — пять строк вашего регистра протухли; аудит корпуса)
|
||||
|
||||
Фронт S4 принят и заленден (D39.135), контракт **0.2.3** в каноне. Для вашего регистра: (1) **PD-172 · PD-173 · PD-174 · PD-180 — диспозиция «спек-правка 0.2.3 задачей S4» (D39.130 п.2в) ИСПОЛНЕНА** — правила стоят в `openapi.yaml` §createBook (PD-172 при этом уточнён ПО ВАШЕМУ коду: чтение останавливается на файле, обязательное после файла = 400 «как не слали», необязательное молча теряется — D39.135 п.2б); обновите статусы. (2) **PD-199 ратифицирован закрытым ещё D39.132 п.2а** («null на проводе подтверждён»), в регистре до сих пор open — привести. (3) **Ваше НОВОЕ обязательство в P7** (D39.135 п.2в): читать `BookIntake.title` (сегодня падает в «unknown field is IGNORED», `v0.go:369-376`) и проецировать `books.reject_reason` на провод (сегодня «kept for an operator and never projected», `v0.go:497`) — без этого 0.2.3 остаётся обещанием без носителя. (4) Кандидаты в P7 по аудиту корпуса 15.08: **PD-219** (упавший дрейн теряет хвост `unit_resolutions` навсегда — зона сама вешала его на читающую поверхность) · **PD-217** (книга на вечном холде блокирует апгрейд движка — вторая половина после D39.132) · **PD-162** (удалённый каталог книги = вечный прогон с открытым холдом). (5) ⚠ Ваш же журнал честно фиксирует: «`tmctl migrate` живьём не гонялся» — команда теперь СУЩЕСТВУЕТ и принята (D39.134); мнение оркестратора: прогнать деплой-рантбук end-to-end на дев-стенде настоящим migrate — обязательное предусловие первого выката, дешевле любого пака. Вопросы — через владельца.
|
||||
Фронт S4 принят и заленден (**D39.135**), контракт 0.2.3 в каноне. Пять статусов регистра (`PD-172`,
|
||||
`PD-173`, `PD-174`, `PD-180` по диспозиции D39.130 п.2в; `PD-199` по D39.132 п.2а) с тех пор приведены —
|
||||
все пять стоят закрытыми эрой P7. Обязательство 0.2.3 (`BookIntake.title` и проекция
|
||||
`books.reject_reason` на провод, D39.135 п.2в) исполнено — `reject_reason` живёт членом ответа книги.
|
||||
Три кандидата аудита 15.08 (`PD-219` упавший дрейн теряет хвост `unit_resolutions` · `PD-217` книга на
|
||||
вечном холде блокирует апгрейд движка · `PD-162` удалённый каталог книги = вечный прогон с открытым
|
||||
холдом) ОСТАЮТСЯ ОТКРЫТЫМИ строками регистра — предмет там, не здесь.
|
||||
⚠ **Живым остаётся одно:** ваш журнал фиксировал «`tmctl migrate` живьём не гонялся», команда с D39.134
|
||||
существует, и мнение оркестратора — прогнать деплой-рантбук end-to-end на дев-стенде настоящим migrate
|
||||
как обязательное предусловие первого выката.
|
||||
|
||||
## Пинг оркестратора №17 (третий) — 16.08.2026 (контракт-ревью принято D39.138: ваша половина батча 0.3.0, PD-104 закрыт, попутные находки)
|
||||
|
||||
Контракт-ревью API v0 отработало отдельной сессией и ПРИНЯТО (отчёт — `docs/research/28-contract-review.md`; решения владельца — его §8, ратификация D39.138). ⚠ Читать ОРИГИНАЛ отчёта, не этот пересказ — он и есть носитель.
|
||||
1. **PD-104 ЗАКРЫТ словом владельца 16.08** (§8 п.14): `SignupGrantMicroUSD` → **0** на бете, начисление руками; возврат $5 — вместе с суточным агрегатным потолком, когда появятся платежи. Исполнение — P7-однострочник конфига + тест (грант при неверифицированном email уже 0, `login.go:306-308`). Обновите регистр.
|
||||
2. **Ваша половина батча 0.3.0 — ПОСЛЕ лендинга спеки; состав — §5 отчёта:** `wireProgress` → один счётчик (Б-0; ваши колонки и словарь шва НЕ трогаются — фазы остаются внутренним делом); словарь машинных `code` + `request_id` в `Problem` (Б-1/§8а: класс 1 — конкретика максимальная, класс 2 модельный — ОДИН грубый код без вариации); `GET /capabilities` (Б-2: пары/порог интейка/форматы/страницы/версия — всё уже есть в конфиге и константах); `WWW-Authenticate` на 401 и две причины 403 (Б-15); трейлинг-части интейка → 400 вместо тихой потери + `Location` на 201 + `Idempotency-Key` (Б-3); подрезание `limit` до максимума вместо сброса к дефолту (Б-10, `books.go:509-511`); `PATCH /books/{id}` title-only + `DELETE` + `GET /runs/{id}` + `Run.failure_reason` (Б-5/Б-8 — словарь исхода у движка уже есть); `blocked: {code, book_id}` на run-options/409 (В-6); отказ по неподдерживаемой паре кодом на интейке (сегодня ja/en-книга умирает `failed`-ом на валидации конфига движка в КОНЦЕ пути — денег не сгорает, но причина не доезжает).
|
||||
3. **Сеть (§5б; вход до первой живой книги под фронтом, строка 186):** gzip на текстовых ответах (НЕ на SSE) и `ETag`/`If-None-Match`→304 на списочных GET — ваше же требование `PLATFORM_DIRECTION.md:202-205`, батчем оно уезжает В КОНТРАКТ. Числа: дерево 2283 глав = 250 КБ на кадр `status`, фокус-рефетч 12 вкладок = 562 КБ.
|
||||
Контракт-ревью API v0 отработало отдельной сессией и ПРИНЯТО (носитель — `docs/research/28-contract-review.md`,
|
||||
решения владельца — его §8, ратификация **D39.138**; читать ОРИГИНАЛ, не пересказ). Три пункта пинга с тех пор
|
||||
ЗАКРЫТЫ и здесь остаются только именами: `PD-104` (грант 0 на бете, начисление руками — §8 п.14) стоит закрытым
|
||||
эрой P7 · половина батча **0.3.0** (состав — §5 отчёта) построена, канон с тех пор ушёл на семь миноров вперёд ·
|
||||
шаги 1-2 сети (gzip и `ETag`/`304`) заленджены P7 (D39.153) и пере-проверены замером — числа живут строкой **186**
|
||||
единого бэклога. Живыми остаются два пункта:
|
||||
|
||||
4. **К-10 (пофазность у главы): НЕ СТРОИТЬ** — поправка приёмки: вердикт §6 отчёта («правка проекции») противоречит Б-0; фазы уходят с провода и у главы.
|
||||
5. **Попутные находки §9** (сессия их адверсариально НЕ судила — проверьте у себя, заведите П/PD-строки по месту): (а) расчёт может висеть навсегда в двух легаси-путях (`reconcile.go:660-670` — попытка без базовой отметки; дев-путь без движка) — бюджета «сколько холд может висеть» не существует; (б) флаг «аккаунт остановлен» выводится сканом последних прогонов ВСЕХ книг (`pgstore/books.go:720-724`) — одна книга зажигает аккаунт; после PD-203 перечитать; (в) `ReadRunForSpawn` линеен по живым прогонам (`pgstore/runs.go:712`); (г) `/metrics` без аутентификации, защита — только 127.0.0.1 (`main.go:200-202`); (д) лимитер входа один на процесс (`login.go:128-129,165`) — один клиент упирает вход всем (выбор объяснён комментарием, следствие стоит записать); (е) сырой Go-текст ошибки лежит причиной карантина в БД (`reconcile.go:320`) — на провод не идёт. Вопросы — через владельца.
|
||||
|
||||
**Аддендум к пингу №17-третьему (16.08, свип планировочных доков D39.139):** (1) `docs/PLATFORM_DIRECTION.md` §2 и `BACKLOG.md` П-7 держат «фри-тир дефолт $5» — ПРОТУХЛО против PD-104 (слово владельца 16.08: грант 0 на бете, начисление руками; D39.138 п.2л) — поправить тексты при P7. (2) Баннер §3 PLATFORM_DIRECTION («ратификация за оркестратором») закрыт ещё D39.132 п.2б: oapi-codegen — кандидат при P7, sqlc — привязан к P7, River-факт поправлен; баннер снять/заменить ссылкой на ноту. (3) §4 п.5 («сервер ВПРАВЕ склеивать события») сузится батчем 0.3.0: склейка только снимкам, `note` не склеивать (research/28 Б-6в). Вопросы — через владельца.
|
||||
|
||||
**Дозакладка к §4.6 промта P7 (16.08, аудит готовности зоны; релей владельца — подтверди эхом):** к списку протухших текстов зоны добавь: `PLATFORM_DIRECTION.md` §4 п.8 («живого канала до-прогонных состояний нет») — решено книжным SSE 0.3.0, опрос запрещён нормой · §4 п.10 («персист манифеста — недостающий») — построен D39.122 · `:184` ссылка на несуществующий §3.3 стандартов · `platform/README.md:3-4` шапка «зона на P1» и «один тест-гейт» (гейта два) · `BACKLOG.md` П-17 — состав пака у́же промта (промт первичен). Расхождение ENGINEERING_STANDARDS:59 против DIRECTION:128 по oapi-codegen решаешь ты (промт §4.6). Всё — попутно, не отдельным заходом.
|
||||
|
||||
|
||||
## Пинг оркестратора №18 — 20.08.2026 (P7 акт 5 ПРИНЯТ С ФИКС-ЛИСТОМ и ЗАЛЕНДЕН; контракт 0.4.0 ратифицирован; тело — D39.152/D39.153)
|
||||
|
||||
**Вердикт: ПРИНЯТЬ С ФИКС-ЛИСТОМ.** Панель шести линз в изолированных копиях (слепая · контракт-конформность · деньги · шов · вне карты · ревью канона 0.4.0 другой моделью) + пере-раны и собственные посадки оркестратора. Регрессов против HEAD нет, ни одной линзы с REJECT.
|
||||
|
|
@ -1787,27 +1733,40 @@ D39.179 п.1 ратифицировал: DeepSeek пере-пинен под ц
|
|||
|
||||
### Фикс-лист приёмки — завести строками СВОЕГО бэклога и регистра (зона моя не пишет)
|
||||
|
||||
⚠ **ПЕРВЫМ — не из пака P7, но найдено вторым рубежом приёмки и проверено мной построчно: `Sweep` останавливается для ВСЕЙ инсталляции на двух медленных прогонах, и выхода нет.** `cmd/tmplatformd/runner.go:189` — `sweepBudget = 2 * time.Minute` на ВЕСЬ проход; `internal/runs/reconcile.go:70` — `defaultRunBudget = 60 * time.Second` на ОДИН прогон. 120/60 = 2: два прогона, выбравшие свой бюджет, съедают проход целиком, и тогда `ctx.Err()` (`reconcile.go:39,48`) обрывает цикл — **`UnsettledRuns`, единственный ретрай отложенного расчёта, не вызывается вообще**, а живые прогоны, стоящие ниже, не реконсилируются и не спавнятся. `ListLiveRuns` сортирует `order by r.started_at` (`pgstore/runs.go:249`), то есть заклиненный прогон — по построению самый старый — стоит в голове и голодит остальных КАЖДЫЙ проход, детерминированно. Ручки нет: `runs.Config.RunBudget` объявлен (`internal/runs/runs.go:62-65`), но `startRunner` его НЕ присваивает (`cmd/tmplatformd/runner.go:74-83`), переменной окружения нет ни для одного из двух чисел, а `tmplatformctl` не умеет ни закрыть прогон, ни вернуть холд. Цена: прогон вечно `finished_at is null` ⇒ холд не возвращается ⇒ `runs_one_live_per_book` не даёт запустить новый прогон этой книги. Деньги заморожены, книга заморожена, пользователю видно «идёт».
|
||||
**Достижимо буднично, без экзотики:** `RunSink.Apply` берёт транзакцию с блокировкой книги на КАЖДУЮ строку журнала (`pgstore/sink.go:80-104`), поэтому после часа простоя демона две книги с бэклогом выбирают проход целиком — и всё время догона деньги всей инсталляции не считаются.
|
||||
⚠ **`PD-169` в вашем регистре стоит `fixed(P5)` и этим ЛЖЁТ приёмке:** его пин гоняет `Sweep` вообще без дедлайна прохода, то есть доказывает пер-прогонный бюджет, а не выживание прохода. Пере-открыть. Код приехал в P6 и в дифф P7 не входил (`Sweep` против HEAD — identical), поэтому ни одна диффовая линза его увидеть не могла: тот же класс «композиция известных фактов», что и строка 198. ⚠ Наблюдаемость при этом ЕСТЬ и она ваша: `tm_platform_sweep_unfinished_total` растёт — то есть оператор увидит, что проход не дошёл до конца, и не сможет ничего сделать. Метрика без ручки — половина механизма.
|
||||
⚠ Список ИСТОРИЧЕСКИЙ — запись о том, что было верно 20.08; задним числом он не переписывается.
|
||||
Пропуски в нумерации — снятые пункты, чьё исполнение проверяемо и чей предмет живёт по адресу:
|
||||
**п.1** `ContractVersion` (поднята F13 до 0.6.0, канон сегодня 0.9.0) · **п.2** пины на
|
||||
`structure_version`/`revision` в кадре (стоят с названной пойманной мутацией в `pgstore/events_test.go`) ·
|
||||
**п.3** бюджет попыток материализации (`PD-357`, закрыт эрой P8-FIX) · **п.5** round-trip в
|
||||
`SaveStructure` (`PD-297`, открытая строка несёт предмет) · **п.7** мусор `platform/ru` (снесён
|
||||
оркестратором тем же заходом) · **п.8** ревью-пак четырёх осей (отработан паком P8-REVIEW, D39.159) ·
|
||||
**п.10** пол на пустой манифест (`readmodel.refreshStructure` сверяет `ChaptersTotal`, тест есть) ·
|
||||
**п.11** `PD-327` (строка закрыта эрой «самопроверка акта 5») · **п.13** оговорка `CreditHeldBy`
|
||||
(запинена с названной пойманной мутацией в `pgstore/credits_test.go`).
|
||||
⚠ **п.12 (изоляция читающих чтений) НЕ закрыт и потому остаётся дословно:** пина на
|
||||
`RepeatableRead`+`ReadOnly` у `inReadTx` в дереве по-прежнему нет.
|
||||
|
||||
⚠ **ПЕРВЫМ — не из пака P7, но найдено вторым рубежом приёмки: `Sweep` останавливается для ВСЕЙ
|
||||
инсталляции на двух медленных прогонах, и выхода нет** (бюджет прохода 2 мин против 60 с на прогон ⇒
|
||||
`UnsettledRuns`, единственный ретрай отложенного расчёта, не вызывается вообще; заклиненный прогон по
|
||||
построению стоит в голове `order by started_at` и голодит остальных детерминированно; деньги и книга
|
||||
заморожены, пользователю видно «идёт»; `tm_platform_sweep_unfinished_total` растёт — метрика без
|
||||
ручки). Тело разбора — **D39.153 §8в**; складывающиеся бюджеты пяти проходов и лечение — строка
|
||||
**`PD-386`**. ⚠ Попутно: `PD-169` стоял `fixed(P5)` и этим ЛГАЛ приёмке (его пин гонял `Sweep` вообще
|
||||
без дедлайна прохода, то есть доказывал пер-прогонный бюджет, а не выживание прохода) — пере-открыт и
|
||||
закрыт эрой P8-FIX.
|
||||
|
||||
4. **sqlc — БЕРЁМ, слово владельца 20.08 («я вообще за»).** ИСПОЛНЕНО: `PD-44` закрыт эрой P12, пак
|
||||
`sqlc` (П-19) отработан секцией выше. Заказанный тем же пунктом ПОСТОЯННЫЙ гейт — прогон каждого
|
||||
собранного запроса через разбор Postgres против мигрированной схемы — построен
|
||||
(`TestEverySQLStatementParsesAgainstTheMigratedSchema`). Разовый ответ, который он тогда дал —
|
||||
141 запрос, все планируются чисто — и поправка к нему («десять непокрытых» неверно, реально три)
|
||||
живут телом **D39.153 §8г**.
|
||||
|
||||
1. **`ContractVersion` = "0.3.0" при формах 0.4.0** (`internal/httpapi/capabilities.go:13`, он же в каждом `hello` — `stream.go:100`). Ратификация 0.4.0 состоялась (D39.152) ⇒ константа обязана подняться. Комментарий над ней сам это требует: «raised in the same commit as the code that implements a new minor». Сегодня практического вреда нет (фронт заморожен на 0.2.3), но деплой объявляет версию, которую не отдаёт. **Первым пунктом.**
|
||||
2. **Пины на два поля `EventBase`** (см. выше). ⚠ Попутно 21.08 усилен гейт якорей доков: `file:line` теперь может нести токен ожидания (`` `путь:12-14`=`подстрока` ``), и такой якорь сверяется ПО СОДЕРЖИМОМУ на каждом прогоне, включая коммиты, которые двигают ЦЕЛЬ. Ваши доки (`platform/docs/**`) вошли в область линта — раньше их не сканировал никто.
|
||||
3. **Бюджет попыток материализации.** У долга нет предела и канала «признать безнадёжным»: claim → fail → defer(`now()`) → следующий свип через 15 с, вечно (`pgstore/books.go:538,557-564`). У интейка предел есть (`parseAttempts`=5). Три следствия: до 5 мин движковых процессов на проход бесконечно · поток такой книги НЕ заканчивается никогда (`AtRest` требует `read_model_owed_at is null`, `events.go:125`) · при живом манифесте и падающем экспорте `SaveStructure` коммитится каждый проход ⇒ `revision++` и кадр каждые 15 с.
|
||||
4. **sqlc — БЕРЁМ, слово владельца 20.08 («я вообще за»).** Отступление P7 закрыто, PD-44 переоткрыть исполнением. ⚠ Ваше предложение «взять на однооператорных ручках P8» покупает инструмент туда, где не болит: рантайм-ошибки «нет такой колонки» случились в СКЛЕЕННОМ SQL read-модели (⚠ счёт: проверяемый носитель — ваш регистр PD-44 и журнал — несёт ДВЕ; ещё две вы назвали в ответе владельцу, в доки они не попали, поэтому опираемся на две — испр. 20.08) (`chapters_before`/`stop_for_signing` — `readmodel.go:441,446`, там же `note_count` и сломанный алиас `b`), куда sqlc по построению не дойдёт. Тем же паком — гейт, который туда дойдёт: прогонять КАЖДЫЙ собранный запрос через разбор Postgres (prepare/describe) против мигрированной схемы; склейка ему не мешает, он получает финальную строку. И ответить попутно на вопрос, которого мы не знаем: **есть ли в read-модели запрос, которого не касается ни один тест** — если есть, это не «медленная обратная связь», а дыра. ⚠ **Половина ответа УЖЕ получена вторым рубежом приёмки** (собранный SQL извлечён из пакета через `go/types` и прогнан `EXPLAIN (GENERIC_PLAN)` против мигрированной схемы): **141 запрос, все планируются чисто, остаточных «нет такой колонки» НЕТ**; тестом ЭТОГО пакета недостижимы десять, но по батарее ЦЕЛИКОМ реально не покрыты только **три** (`DeleteOldLoginEvents` · `UserByIdentity` · `Observe`; остальные семь покрыты на 58–87% тестами соседних пакетов — пере-мерено покрытием при аудите 20.08, первая редакция называла десять и это было неверно) — `RefundParseAttempt` · `StuckIntake` · `OpenReservations` · `ReleaseUnspawned` · `DeleteOldLoginEvents` · `UserByIdentity` · `Observe` · `SpendBound` · `AttemptReservationOpen` · `RunPausedReason`, и ни один из них не трогает схему 00016–00024. То есть дыры сегодня нет, а гейт нужен как ПОСТОЯННЫЙ — именно он и делает этот ответ воспроизводимым.
|
||||
5. **PD-297** — round-trip на строку в `SaveStructure` под эксклюзивной блокировкой книги; мерить на корпусной книге до и после (корпусная книга появится на холодном прогоне движка).
|
||||
6. **Труба доставки решений банка в движок** — единый бэклог, строка **199(а)**: перед resume писать решения в `mined_delta`/`mined_rejects`. Сегодня `action`/`dst` — write-only колонки (единственный SELECT `readmodel.go:336` проверяет лишь наличие строки), а воркер описан в комментарии вашей же миграции `00002_readmodel.sql:172-174` и не построен.
|
||||
7. **Мусор:** пустой `platform/ru` (0 байт, обрубок редиректа) — в лендинг не взят, снесён оркестратором. `books.go:234-235` — задвоенная первая строка доккомментария, класс PD-310/326.
|
||||
8. **Ревью-пак четырёх осей, которых не смотрел НИКТО** (ваш же obstacle): деньги и леджер целиком · вход/сессии/CSRF · очередь и джобы · метрики. Это не дофикс и не довесок — первый взгляд, отдельной работой.
|
||||
9. **Один носитель на факт для замера 00016→00022 — но НЕ удалением числа.** Выигрыш назван ЧЕТЫРЬМЯ носителями: шапка этого журнала «83%» · `internal/pgstore/perf_test.go:13`=`96% of the page` «96%» · миграция 00022 «16.8 против 3.4, джойн сам 9 мс» · регистр PD-306 (повторяет 83% и 16.8/3.4). ⚠ Я сперва записал, что «96%» ни из чего не выводится — **это была моя ошибка, снята проверкой записей**: оно выводится точно из ВАШЕГО же замера в `archive/P7_ACT5_FIX_PLAN_2026-08-20.md:444-446`=`636 мс против` («636 мс против 24 мс», холодный корпус акта 4) = 96.2%, тогда как 83% и мой независимый ≈80% — с вакуумированного корпуса. То есть носители меряли РАЗНОЕ и все честны. Свести указанием УСЛОВИЙ замера при каждом числе, оставив нормативным один (миграция 00022 — она их и несёт); удалять «96%» как фантом НЕЛЬЗЯ.
|
||||
10. **У материализатора нет ПОЛА на пустой манифест — латентная потеря всего текста книги.** `internal/readmodel/readmodel.go:215-232` строит `in.Chapters` только из `manifest.Chapters` и ни разу не сверяется со счётчиками того же документа (`ChaptersTotal`/`UnitsTotal` лежат рядом и печатаются в лог строкой ниже). Пустой список едет в `SaveStructure`, где `pgstore/readmodel.go:193-196` выполняет `delete from chapters where book_id = $1 and not (id = any($2))` — на пустом массиве предикат истинен для ВСЕХ глав, и каскад `chapters → units` сносит текст; при пустом `Key` вдобавок срабатывает `changed` и уходят все `unit_resolutions` (родня строки 198). Сегодняшним движком недостижимо (`buildManifest` всегда наполняет главы, файл пишется атомарно) ⇒ фикс-лист, не блокер. **Асимметрия и есть находка:** на ИНТЕЙКЕ ровно этот случай отловлен явно и прибит мутацией (`internal/books/parse.go:134-142`, `books_test.go:1296`) — «нет глав, но работа считается» там не признаётся правдой о книге. У материализатора такого пола нет, и теста на нулевой манифест в `readmodel_test.go` тоже нет. Лечится одной сверкой `len(manifest.Chapters)` против `manifest.ChaptersTotal`.
|
||||
11. **`PD-327` в регистре стоит `open`, а канон 0.4.0 РАТИФИЦИРОВАН** (D39.152) — то есть условие её закрытия наступило. Закрыть строку регистра явно; сегодня расхождение видно скриптом (`counts.py --check` печатает PD-327 среди открытых), и это ровно тот класс, ради которого регистр объявлен источником истины по статусу.
|
||||
|
||||
12. **Изоляция читающих чтений НЕ ЗАПИНЕНА (аудит 21.08, посадка мутации).** Снятие `RepeatableRead`+`ReadOnly` у `inReadTx` (`internal/pgstore/credits.go:492-493`) проходит ВСЮ батарею — при том, что под этим инвариантом лежат шесть ручек выдачи, а носитель прямо объясняет цену («every frame in that window was lost for good», `books.go:684-688`, PD-163). По вашей же норме PD-1 свойство без пинящего теста считается НЕ закрытым — а это тот самый класс, которым я мерил найденную дыру с `emitFrame`. Код приехал НОВЫМ в P7, то есть лежал внутри диффа, который читали шесть линз приёмки и я сам.
|
||||
|
||||
13. **`CreditHeldBy`: оговорка `and book_id <> $2` не запинена (аудит 21.08, посадка мутации).** Мутант, снимающий её, проходит батарею: пин, который выглядит покрывающим, её ни разу не исполняет — в фикстуре у исключаемой книги холда НЕТ (`internal/pgstore/credits_test.go:443-465`). Сумма кормит контрактный `blocked`, и раздутая сумма отправляет пользователя гасить прогон, который ничего не освободит — ровно тот вред, против которого написан комментарий над самим запросом (`credits.go:393-397`).
|
||||
|
||||
### Что ушло в ЕДИНЫЙ бэклог (движок/шов/контракт — ваши строки туда не заходят, D39.84)
|
||||
|
||||
**198** апгрейд движка стирает замечания и счётчики безвозвратно (ваша зачистка при ре-кате × announce-once движка — композиция, гейт холодного прогона) · **199** канал доставки правок банка · **200** сквозная полоса прогресса вместо пофазной (слово владельца 20.08) · **201** «Глава N» внутри текста экспорта · **202** живой прогон насквозь через API — ПОСЛЕ холодного прогона движка (слово владельца 20.08) · **203** хвосты контракта · **204** движок публикует причины флагов данными (релей §7в, ваш PD-246 — строка заведена в бэклоге ДВИЖКА, как вы и просили). ⚠ По релеям §7 сверено грепом при лендинге, а не по вашему списку: **(б) и (д) уже ИСПОЛНЕНЫ** синком 0.4.0, **(г) наполовину** (`unspecified` ратифицирован, открыта граница ступеней), **(и)** закрыт полем `stop_requested`. Реально открыты только (а)-канон-половина, (з) и договорная часть (к).
|
||||
|
|
@ -1818,7 +1777,5 @@ D39.179 п.1 ратифицировал: DeepSeek пере-пинен под ц
|
|||
|
||||
**Оговорка про пер-термные решения расширена на ВСЮ ручку** (была только на `decline`): инертны одинаково и `approve`, и `dst`. Оговорка временная — снимается исполнением строки 199(а).
|
||||
|
||||
⚠ **Исправление МОЕГО же пинга №15 (испр. оркестратором №18):** «Все сайдкары пишутся атомарно (temp+rename)» — НЕВЕРНО. Атомарны три из шести (`manifest.json`, `bank.json`, `bank-stop.json` — `backend/internal/pipeline/artifact.go:24-59`); `bank-stop.txt`, `mined-signature.yaml`, `auto-bank.yaml` пишутся `os.WriteFile` с усечением. Сегодня безвредно (читателей нет), но сессия, взявшая любой из них по той строке, получит усечённый документ на живом прогоне.
|
||||
|
||||
**Сессия P7 ЗАКРЫТА владельцем 20.08.** Промт отработан → `platform/docs/archive/`. Дальше — другие сессии.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue