40 lines
30 KiB
Markdown
40 lines
30 KiB
Markdown
# Бэклог платформы (зонный)
|
||
|
||
> Ведёт зона `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 |
|
||
| П-22 | ✅ **ЗАКРЫТА 11.09 — вместе с предметом.** Строка просила решить, каким шаблоном поднимать второй гейт батареи, потому что $0-шаблон давал два красных теста, а платный якобы тратил деньги. **Обе половины посылки оказались ложными, и вторая — денежная.** Живая проба НЕ получает пайплайн шаблона: `writeProbeBook` БЕЗУСЛОВНО выводит из него $0-пайплайн (`zeroCostPipeline`) и указывает книгу на него; комментарий того же хелпера говорит это прямым текстом, включая «a paid model, on every stand built by the recipe in STACK_DECISIONS». ⇒ $0-шаблон никогда не берёг денег, развилки между шаблонами не существовало, а гард согласия, построенный под неё, спрашивал о документе, который движок не грузит. **Что сделано:** рецепт $0-шаблона снесён целиком (две его редакции — первая оставляла вендоров под ключами эскалации, вторая производила файл, который движок отказывается грузить: `escalate_to must differ from the primary model`, `EXIT=10`); гард переставлен на РЕНДЕР — файл, который проба кладёт рядом с книгой. Вендор доходит до провайдера только одним способом, ПЕРЕЖИВ дериват, и гард, не зависящий от имён ключей, такой промах увидит; дериват же — рерайтер ПО КЛЮЧАМ, то есть класс, который однажды уже промахнулся. Пины: `runner.TestTheGuardSeesAVendorThatSurvivedTheZeroCostDerivation` и `runner.TestTheRenderOfAnOrdinaryPaidTemplateComesOutFree`. ⭐ **Урок, который дороже строки: ответ лежал в ДЕРЕВЕ, комментарием того самого хелпера, строкой выше того места, где остановилось чтение.** Механизм строился под задачу, которую дерево уже решило и задокументировало | закрыта | замер зоны 11.09 (пак «ответ двери»), закрыта вторым кругом приёмки смены №23 |
|
||
| П-23 | ✅ **ЗАКРЫТА 11.09 НЕ ИСПОЛНЕНИЕМ, А ИСЧЕЗНОВЕНИЕМ ПРЕДМЕТА.** Строка несла принятый размен «мина снапшот-гарда по умолчанию не стережётся», купленный выбором платного шаблона по развилке П-22. Размен был оплачен риском, которого на этом пути нет: гард теперь спрашивает о РЕНДЕРЕ, рендер и правда $0, поэтому живая проба бежит по умолчанию и мина стережётся. Замер: с настоящим `backend/example/book.yaml` тест доходит до своего следующего условия (артефакт контраста), а не скипается по согласию. ⚠ Держать носитель размена, которого больше нет, значило бы оставить следующей смене ложный след | закрыта | решение оркестратора №23 по П-22, снято вторым кругом приёмки 11.09 |
|
||
| П-24 | **У «прогонов, на которых платформа сдалась» нет ретроспективной ручки.** Пак «прогон не врёт о себе» построил предел перезапусков (ряд 398): исчерпание объявляется ERROR-строкой, попытка несёт своё слово `attempts-exhausted` в `run_attempts.exit_result`, а прогон уезжает `failed`/`service_error`. Чего НЕТ: команды, которой оператор спросит «какие прогоны мы бросили сами» ПОСЛЕ события — операторский листинг (`tmplatformctl runs`) показывает живые и застрявшие в расчёте, а исчерпавший предел рассчитывается сразу и из листинга уходит. Сегодня ответ добывается только SQL по колонке. ⚠ Сознательно НЕ построено в паке: новая операторская поверхность — это заказ, а не хвост капа, и предмет весит `info` (факт записан, момент объявлен). Лечить: либо фильтр к `runs` по исходу попытки, либо `run show --run <id>` | когда-нибудь | пак «прогон не врёт о себе» 17.09, названо в отчёте как остаток |
|
||
| П-25 | **Отказ двери правок банка при невозможности записать намерение НЕ ЗАПИНЕН, и запинить его нечем без шва.** Дверь (`internal/runs/bank.go`) теперь ОТКАЗЫВАЕТ (`ErrBankUnavailable`), если write-ahead отметка не записалась: без неё смерть процесса между глаголом и штампом снова без канала (ряд 400). Ветка проверена чтением и построением, но не исполнением: `Service.Store` — конкретный `*pgstore.Store`, а не интерфейс, поэтому подставить стор, который отказывает на ОДНОМ методе, нельзя, а рвать пул убивает и остальные чтения того же вызова. ⇒ мой прибор здесь слеп, и это названо, а не спрятано. Лечить: либо узкий интерфейс на пишущие методы двери (шов ради тестируемости — решение зоны, не моё), либо инъекция сбоя на уровне пула | когда-нибудь | пак «прогон не врёт о себе» 17.09, блок «где прибор слеп» |
|