textmachine/platform/BACKLOG.md

30 KiB
Raw Permalink Blame History

Бэклог платформы (зонный)

Ведёт зона platform/ (решение владельца 02.08, D39.84: фронт и платформа держат СВОИ бэклоги; единый бэклог docs/PROGRESS.md остаётся трекером движка/полигона/доков и фронт/платформа-строк не принимает). Нормы те же: ID стабилен навсегда, каждая петля получает диспозицию. Запросы к ДВИЖКУ сюда не пишутся — они заходят строками единого бэклога через оркестратора (пример: строки 99102). Засеян оркестратором при лендинге 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 владелец 0405.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/1019 через 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 (1617.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.058bca19, D39.194 п.3); смонтировано 18 маршрутов. ⚠ updateBook ПОСТРОЕН 05.09 паком операций — PATCH /books/{bookId}, по букве канона и без его правки (операция была объявлена и не смонтирована); маршрутов стало 19. Остаются непостроенными deleteBook · getRundeleteBook теперь стоит дороже: у книги появились АРТЕФАКТЫ на диске, и удаление обязано снимать и строки 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 ФЛЕЙКОВЫЙ, 12 падения на ~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, блок «где прибор слеп»