# Бэклог платформы (зонный) > Ведёт зона `platform/` (решение владельца 02.08, D39.84: фронт и платформа держат СВОИ бэклоги; единый бэклог `docs/PROGRESS.md` остаётся трекером движка/полигона/доков и фронт/платформа-строк не принимает). Нормы те же: ID стабилен навсегда, каждая петля получает диспозицию. Запросы к ДВИЖКУ сюда не пишутся — они заходят строками единого бэклога через оркестратора (пример: строки 99–102). Засеян оркестратором при лендинге D39.84 — дальше правит платформа-сессия. > **Диспозиции после P1 (05.08)** — раздел «Диспозиции бэклога зоны» архив-слайса зонного журнала > (`docs/archive/platform-PROGRESS-P0-P3.md`). Коротко: П-6 и П-8 ЗАКРЫТЫ, П-7 закрыт по схеме и операциям > (постановка холда воркером — часть П-1), П-4 отменён и поглощён П-7, П-5 переопределён под > кредитную модель и ждёт правки спеки, П-1 продолжена, П-2/П-3 не трогали. > Дублировать их здесь не стали — у строки один источник истины. > ⚠ Сводка датирована 05.08 и задним числом не переписывается, но по П-5 она УСТАРЕЛА: спека с тех > пор правлена, ручка построена — актуальное состояние в самой строке П-5 ниже. Адрес слайса > file-относительный: `platform/docs/archive/platform-PROGRESS-P0-P3.md:1086`=`## Диспозиции бэклога зоны`. | ID | Хвост | Вес | Источник | |---|---|---|---| | П-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` (довод — `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` нет, **ручка ПОСТРОЕНА, спека правлена** — канон несёт `docs/architecture/14-api-contract/openapi.yaml:815`=`operationId: getUsage` с прямым «no period, no `resets_at`», маршрут смонтирован (`platform/internal/httpapi/v0.go:89`=`path: "/usage"`, греп `"/usage"`; хендлер — символ `func (h *v0) usage`). Диспозиция П-5 с тремя поправками (колонка `Run.paused_reason` · скоуп `revision` · трактовка D39.84) — `platform/docs/archive/platform-PROGRESS-P0-P3.md:861`=`потолок поднимает платформа) — ПРИНЯТ` | Ф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 | ✅ **ЗАКРЫТА 06.09 — построено движком и взято платформой.** Строка просила «честную оценку $/глава от ДВИЖКА вместо константы платформы». Пере-снято по дереву: константы `DefaultPerChapter` ($0.03) больше нет — `internal/pricing/pricing.go` начинается с `THERE IS NO PER-CHAPTER RATE HERE ANY MORE`; цену даёт движковая проекция по КОНКРЕТНОЙ книге (`expected_usd` per chapter, `book_once_usd`, `step_max_usd`, `source_chars` в `manifest --json`; лендинг `81a89e9`, акт **D39.206**), а книга, которую движок оценить не смог, ОТКАЗЫВАЕТСЯ вместо продажи по догадке. Все четыре несущих утверждения строки ложны против дерева ⇒ закрываю актом зоны | закрыта | сессия P4 → закрыто паком «деньги и правда на экране», 06.09 | | П-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):** продуктовых КВОТ НЕТ и не будет, фри-тир-лимиты не проектируются, живём на покупке API и зачислениях из админки; «сколько книг входит во фри-тир» — больше НЕ развилка и НЕ ждёт владельца. Остаётся ИНЖЕНЕРНАЯ гигиена, решаемая зоной без чьего-либо слова: ретеншен отклонённых (`rejected`) книг · свип каталогов-сирот · потолок диска. Гейт у неё один и он не продуктовый — открытая регистрация, которой в закрытой бете нет. | инженерная гигиена, до открытой регистрации | сессия P5; продуктовая половина снята D39.176 | | П-13 | **Оценка размера книги в символах — у ДВИЖКА.** `character_count` контракта платформа считает потоково на приёме (байты, не являющиеся продолжением UTF-8). ⚠ **Формулировка «точно для UTF-8 и приблизительно для GB18030/UTF-16» исправлена 05.09 — она была УЖЕ правды и пропускала худший случай:** интейк принимает `.epub`, и тогда считаются руны ZIP-АРХИВА, то есть не приближение числа знаков, а свойство контейнера. Запинено `internal/books/counter_test.go`. Манифест несёт `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`); правила шаблона и его отказов — `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). ⚠ **ЭКСПОРТ ПОСТРОЕН 04.09** паком «закрыть цикл» — `createExport`/`getExport` плюс третий адрес `.../content` (канон дал ему `operationId` `downloadExport` минором `0.10.0` — `58bca19`, `D39.194` п.3); смонтировано 18 маршрутов. ⚠ **`updateBook` ПОСТРОЕН 05.09** паком операций — `PATCH /books/{bookId}`, по букве канона и без его правки (операция была объявлена и не смонтирована); маршрутов стало **19**. Остаются непостроенными `deleteBook` · `getRun` (и `deleteBook` теперь стоит дороже: у книги появились АРТЕФАКТЫ на диске, и удаление обязано снимать и строки `exports`, и файлы — каскад по строкам есть, по файлам нет) | П-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`** — ⚠ **ИСПОЛНЕНО отдельной сессией 29.08 и заленджено (D39.172; `PD-44` закрыт).** Отдельной сессией, а не внутри пака, — решение владельца 22.08: изменений много, мешать их с чем-либо нельзя. Что построено (пин v1.31.1, `sqlc.yaml`, генерённый код в дереве, два гейта актуальности, override `*.*_micro_usd` на деньги) — `docs/STACK_DECISIONS.md`, строка «Кодоген SQL». ⚠ Живая ГРАНИЦА набора, которую стоит помнить при следующем касании: **42 места вызова / 41 текст SQL / 40 конвертируемых**; `observe.go` структурно не конвертируется — River мигрирует `river_job` сам, и внесение чужой схемы в конфиг завело бы ВТОРОЙ её носитель (испр. D39.172), поэтому худший позиционный дрейф набора — семь `int64` подряд — остался рукописным. Остальные 25 склеенных мест недостижимы по построению (`lastRun` — девять потребителей, `nextRevisionOfThisBooksLibrary` — восемь), и это ровно та часть, где рантайм-ошибки и случались. ⚠ Пак покупал типизированные скан-структуры и раннюю обратную связь, а НЕ корректность: класс «нет такой колонки» закрыт постоянным гейтом `TestEverySQLStatementParsesAgainstTheMigratedSchema` (пол — 140 операторов; после конверсии видит 172 — числа в `PD-44`) | ИСПОЛНЕНО (D39.172) | PD-44, фикс-лист приёмки P7 п. 4, решения владельца 20 и 22.08 | | П-20 | **Хвост PD-162 после P8-FIX: холд прогона, чей движок не ответит НИКОГДА, возвращается только рукой.** Пак дал оператору и диагноз (`tmplatformctl runs --stalled`, гейдж `tm_platform_runs_stalled`), и терминальный вердикт (`run abandon [--release-hold]`), и это сознательная граница: автоматически решать про деньги прогона, о котором нельзя спросить, — та же ошибка «я не смог спросить = его нет», только со счётчиком впереди. Автоматический ответ на этот вопрос и есть эскроу — write-ahead intent · `uncertain` · `closing` (строка 136 единого бэклога, П-18). Пока эскроу нет, ручка остаётся ручной, и это записано, а не подразумевается | вместе с П-18 | P8-FIX | | П-21 | **Ключ идемпотентности под конкуренцией даёт 500 (PD-369).** ⚠ Не работа пака P8-FIX и файл им не тронут — найдено попутно, прогоном батареи: собственный тест `TestAClaimThatLostARaceToAReleaseIsRetriedAndNotAnError` ФЛЕЙКОВЫЙ, 1–2 падения на ~80 прогонов. Причина не в тесте: `ClaimIdempotency` ретраит проигранную гонку ровно `claimRounds = 3` раза, а восемь одновременных попыток одного ключа, каждая из которых сразу отдаёт ключ назад (как делает любой 4xx), могут отобрать гонку у одного проигравшего трижды подряд — и он получает внутреннюю ошибку вместо одного из четырёх контрактных ответов. ⚠ **ЗАКРЫТО паком P12** — второй из двух названных форм, ответом, а не границей по времени: `ErrKeyContended` оборачивает `ErrKeyInFlight` (`internal/pgstore/idempotency.go:92`), словарь кодов контракта не расширен, исчерпание раундов стало контрактным ответом вместо `500`. Регистр — `PD-369` = `fixed(пак P12)`. Строка держалась развилкой после того, как развилка была разрешена кодом | ИСПОЛНЕНО (пак P12) | попутная находка сессии P8-FIX |