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

This commit is contained in:
heaven 2026-08-05 00:37:29 +03:00
parent 52e12f311e
commit 87be7b9238
3 changed files with 43 additions and 52 deletions

View file

@ -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 | владелец 0405.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 |

View file

@ -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. Стандарты и стек: что взять, что не брать

View file

@ -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` существуют ДО прогона, и библиотека охватывает книги без прогонов.
Живого канала у них нет вовсе. Нужна либо строка в спеке «до старта прогона состояние