From 87be7b9238b5fd48c1872a495a4d01c846090abd Mon Sep 17 00:00:00 2001 From: heaven Date: Wed, 5 Aug 2026 00:37:29 +0300 Subject: [PATCH] Record owner decisions on credits: no payments in beta, credit balance instead of reset windows, five dollar admin-granted free tier, and withdraw the unfounded payment-geography claim --- platform/BACKLOG.md | 3 +- platform/docs/PLATFORM_DIRECTION.md | 72 +++++++++++++---------------- platform/docs/platform-PROGRESS.md | 20 ++++---- 3 files changed, 43 insertions(+), 52 deletions(-) diff --git a/platform/BACKLOG.md b/platform/BACKLOG.md index 44dbd007..11b73a1d 100644 --- a/platform/BACKLOG.md +++ b/platform/BACKLOG.md @@ -13,5 +13,6 @@ | П-3 | **Очередь и конкурентность по книге**: River на том же Postgres, сериализация по `book_id` + пиннинг книги к одному хосту на MVP, лизы `book_leases` с heartbeat — вежливое ожидание вместо аварии | Ф3-пак платформы | STACK_DECISIONS §5, 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 | **Фри-тир + покупка токенов «как у OpenRouter»** (направление владельца 04.08): кредитный леджер в целых микро-долларах, идемпотентные списания, резервация ДО старта прогона, окна фри-тира; продажа кредитов — через провайдера с минимальной комплаенс-поверхностью (merchant-of-record vs Stripe — решение по ресёрчу). ⚠ Гейт: пер-пользовательский учёт отстаёт на попытку, пока не решён открытый канон-вопрос об источнике денег во время попытки (журнал зоны, «Ратификация приёмкой») | P1+, после П-6 | владелец 04.08 | +| П-7 | **Кредитный баланс и фри-тир** (решения владельца 05.08; ПРОДАЖИ НЕТ — бета на предоплаченных ключах владельца): append-only леджер в целых микро-долларах, грант фри-тира из админки (дефолт $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 | diff --git a/platform/docs/PLATFORM_DIRECTION.md b/platform/docs/PLATFORM_DIRECTION.md index 6e791b66..f1142c2b 100644 --- a/platform/docs/PLATFORM_DIRECTION.md +++ b/platform/docs/PLATFORM_DIRECTION.md @@ -44,50 +44,44 @@ hosted IdP (связка PII + доступность, а свою сессию 5. Первая НЕаутентифицированная ручка (`/auth/login`) — первая же поверхность для ботов: лимит на исходящие state-записи обязателен вместе с ней. -## 2. Деньги: фри-тир и покупка токенов (П-7) +## 2. Кредиты и лимиты (П-5/П-7) — решения владельца 05.08 -**Форма «как у OpenRouter» — предоплаченный кредитный леджер с резервацией ДО работы** (сверено по -публичным докам OpenRouter · Anthropic · Modal). **Фри-тир — периодическим ГРАНТОМ в тот же леджер** -(механика Modal): отдельного кода фри-тира не существует вовсе. +**Оплаты нет и в бете не будет.** Первые аккаунты — пробные, сидят на фри-тире, ключи провайдеров +предоплачены владельцем. Платёжный провайдер не выбирается, не проектируется и в бэклог зоны как +работа не заходит; вернуться — когда появится решение продавать. ⚠ Прежняя редакция этого раздела +несла вывод «покупателям из России платить нечем» — он был ВЫВЕДЕН из целевого языка перевода, а не +установлен, и снят как необоснованный (владелец 05.08). Язык книги о географии плательщика не говорит. -**Минимальная схема (строить с П-7, не раньше):** к существующему `usage_windows` — три таблицы: -`credit_ledger` (append-only, знаковые целые микро-доллары, типы `grant|purchase|hold|hold_release|settlement|adjustment`, -`UNIQUE(source, source_id)` как ключ идемпотентности), `reservations` (одна открытая на -`engine_run_id`), `account_balances` (кэш баланса в ТОЙ ЖЕ транзакции, что вставка в леджер, + -тест-инвариант `balance == SUM(ledger)`). Правки строк не существует по построению: ошибка чинится -новой записью. Дробей нет нигде — только целые микро-доллары. +**Модель лимита — БАЛАНС кредитов, а не окна с обнулением** (владелец 05.08: «не подписки, а покупка +токенов как у OpenRouter»). Следствия, несущие для схемы и контракта: +- Понятия «окно сброса» и `resets_at` НЕТ. Черновик `usage_windows` (00003) в этой форме не годится: + его `period=day|week` + `limit_micro_usd` — подписочная механика. Заменяется балансом и леджером. +- ⚠ Это **отменяет оконную рамку П-5** (страница лимитов «как Claude Code», D39.100/ПТ-35) в части + сброса окон. Что остаётся из D39.100 без изменений: суммы на экран не идут, стоп по исчерпании = + статус `paused` + оповещение. Экран показывает ОСТАТОК (процентом от гранта), а не «сбросится через». +- Вопрос «продолжать ли автоматически после сброса лимитов» отпадает вместе с окнами. Осталось: + после пополнения баланса прогон возобновляется явным действием (кнопка/админ), а не сам. -**Когда резервируем:** в той же транзакции Postgres, что допускает задачу в очередь, ДО спавна -`tmctl`; движку передаём пер-книжный потолок ≤ зарезервированного, и тогда ЕГО собственный -жёсткий стоп остаётся последним рубежом, а наша резервация может быть консервативной, а не -реал-таймовой. Расчёт — на границе попытки, идемпотентно по `(engine_run_id, attempt)`. +**Фри-тир = ГРАНТ в леджер, управляется из админки** (владелец 05.08). Дефолт **$5**, настраиваемый +пер-аккаунт; «накинуть кредитов» = одна запись леджера типа `grant`, отдельного кода фри-тира не +существует. Это и есть стандартная механика (Modal и другие делают так же): начисление и есть весь +фри-тир. Нужна минимальная админ-поверхность — защищённая ручка или CLI-команда, пишущая грант. -**⚠ Открытый вопрос владельцу №1 (продуктовый, не технический): платить из России нечем.** Paddle -собственной политикой блокирует покупателей в России и Беларуси; у западных PSP compliant-пути нет. -Для продукта, чей целевой язык — русский, это не деталь выбора вендора, а вопрос рынка и способа -оплаты. Решать до того, как строить П-7. +**Схема (строить с П-7):** `credit_ledger` (append-only, знаковые целые микро-доллары, типы +`grant|hold|hold_release|settlement|adjustment`; `UNIQUE(source, source_id)` — ключ идемпотентности; +`purchase` добавится, если появится продажа), `reservations` (одна открытая на `engine_run_id`), +`account_balances` (кэш в ТОЙ ЖЕ транзакции, что вставка в леджер, + тест-инвариант +`balance == SUM(ledger)`). Правки строк не существует: ошибка чинится новой записью. Только целые +микро-доллары; на шве расчёта округляем ВВЕРХ (леджер движка — нижняя граница). -**Платёжный провайдер — НЕ выбираем сейчас** (пользователей ноль). Когда дойдёт: только -merchant-of-record, шорт-лист **Paddle vs Stripe Managed Payments** — ⚠ поправка скептика: у Stripe -с февраля 2026 есть собственный MoR-продукт (Stripe — зарегистрированный продавец, налоги в 80+ -странах), что снимает прежний аргумент «Paddle или сам плати VAT OSS»; Lemon Squeezy как независимый -игрок мёртв (куплен Stripe в 2024). Покупка проектируется как ОДНА запись леджера с id события PSP -в качестве ключа идемпотентности — тогда выбор вендора остаётся дешёвым. - -**Хвосты, которые нельзя забыть** (скептик): чарджбэк приходит после того, как токены потрачены ⇒ -леджер обязан переживать отрицательный баланс и иметь политику для прогонов в полёте · MoR продаёт -в валюте покупателя, а леджер — микро-доллары ⇒ либо фиксированные долларовые паки, либо правило FX -на момент покупки · движок отдаёт `committed_usd` как float64, а леджер целочисленный ⇒ правило -округления на шве расчёта назвать явно (леджер движка = НИЖНЯЯ граница, значит округляем вверх). - -**⚠ Открытый вопрос владельцу №2: источника денег ВО ВРЕМЯ попытки не существует.** Движок держит -эксклюзивный лок (`status --json` недоступен), его stderr-INFO с ценами парсить запрещено доктриной, -словарь событий денег не несёт. Варианты: **(A)** версионированное денежное событие в словаре строки -103 — свежесть до стадии, но трогает движок и отменяет действующую доктрину «поток цифр не несёт»; -**(B)** консервативные пер-книжные потолки, расчёт на границе попытки — ноль правок движка, -пользователь не может перерасходовать сверх суммы открытых холдов, цена — пере-резервация; -**(C)** нарезка попыток. **Рекомендация оркестратора: (B) сейчас, (A) — отдельным решением, если -замер покажет, что пере-резервация душит фри-тир.** Строить П-7 можно на (B) без ожидания ответа. +**Резервация — РЕШЕНО (вариант B, оркестратор 05.08; вопрос владельцу снят как переусложнённый):** +в той же транзакции, что допускает задачу в очередь, ДО спавна `tmctl` ставим холд и передаём движку +пер-книжный потолок ≤ остатка баланса. Дальше жёсткий стоп исполняет САМ движок, у которого этот +механизм уже есть, — поэтому перерасход физически невозможен, даже если платформа во время прогона +не знает ни цифры. Единственное видимое следствие: индикатор остатка обновляется на границах +попытки, а не непрерывно. Денежное событие в потоке движка (вариант A) не строим — оно трогает +движок ради обновления индикатора; вернуться, только если замер покажет, что грубость холда душит +фри-тир. ## 3. Стандарты и стек: что взять, что не брать diff --git a/platform/docs/platform-PROGRESS.md b/platform/docs/platform-PROGRESS.md index dae62aad..edca491d 100644 --- a/platform/docs/platform-PROGRESS.md +++ b/platform/docs/platform-PROGRESS.md @@ -19,19 +19,15 @@ ## Открытые вопросы к владельцу/оркестратору -**⚠ ВЛАДЕЛЬЦУ, добавлено приёмкой 05.08 (разбор — `PLATFORM_DIRECTION.md`):** -0а. **Платить из России нечем.** Paddle своей политикой блокирует покупателей из РФ и Беларуси, - compliant-пути у западных PSP нет. Для продукта с русским целевым языком это вопрос рынка и - способа оплаты, а не выбора вендора — решать ДО стройки П-7. -0б. **Источник денег во время попытки** (движок держит лок, stderr-INFO парсить запрещено, поток - цифр не несёт): (A) версионированное денежное событие в словаре строки 103 — трогает движок и - отменяет действующую доктрину · (B) консервативные потолки + расчёт на границе попытки — ноль - правок движка · (C) нарезка попыток. **Рекомендация: (B) сейчас**; строить П-7 можно, не дожидаясь. -0в. **Размер и форма фри-тира** (грант в тот же леджер — механика выбрана; число — за владельцем). +**Решения владельца 05.08 (закрыли всё, что висело по деньгам):** оплаты нет и в бете не будет — +пробные аккаунты на фри-тире, ключи предоплачены владельцем · модель лимита = БАЛАНС кредитов, а не +окна с обнулением («не подписки, а покупка токенов как у OpenRouter») ⇒ `resets_at`/`usage_windows` +в подписочной форме отменены, вопрос авто-резюме после сброса окон отпал вместе с окнами · фри-тир = +грант в леджер из админки, дефолт $5, настраиваемый. ⚠ Мой вывод «покупателям из России платить +нечем» СНЯТ как необоснованный: он был выведен из целевого языка перевода, а не установлен. +Резервация решена оркестратором (вариант B — холд + пер-книжный потолок движку, жёсткий стоп исполняет +движок). Разбор — `PLATFORM_DIRECTION.md` §2. -1. **⚠ ВЛАДЕЛЬЦУ (П-5).** При сбросе окна лимитов приостановленный (`paused`) перевод - продолжается САМ или ждёт явного «Продолжить»? От ответа зависит, есть ли кнопка на экране и - нужно ли уведомление «продолжили без вас». Технически дёшевы оба варианта. 2. **Оркестратору (контракт).** Поток событий привязан к ПРОГОНУ (`/runs/{runId}/events`), а статусы `uploading`/`parsing` существуют ДО прогона, и библиотека охватывает книги без прогонов. Живого канала у них нет вовсе. Нужна либо строка в спеке «до старта прогона состояние