Квоты и расходы — пределы использования и трат
Движок квоты покрывает три рода: квоту токенов и квоту хранилища (Phase 207) плюс квоту расхода (Phase 216, hold-and-settle над математикой настоящих денег). Квоты — инструмент оператора для потолков использования в многопользовательских установках; они …
Движок квоты покрывает три рода: квоту токенов и квоту хранилища (Phase 207) плюс квоту расхода (Phase 216, hold-and-settle над математикой настоящих денег). Квоты — инструмент оператора для потолков использования в многопользовательских установках; они связывают каждого пользователя с настроенной квотой — администраторов включительно — а незаданная квота = без ограничений (fail-open: ноль изменения поведения для существующих установок).
Функция Community (инструмент оператора). Источники:docs/QUOTAS.md(Phase 207 + 216),docs/MODEL_COSTS.md,docs/API.md(секция Costs, quotas, and per-user model policy). Строоки происхождения:FACT-CENSUS.mdF-QUOTA-1.
Ядро-инварианты
- Никакого второго счётчика. Правда о потреблении (токены/расход) остаётся в существующем ledger
WorkspaceTokenUsage(packages/server/src/services/quotaService.ts).QuotaBalance/QuotaHold/QuotaReset= производное и заякоренное состояние, никогда таблица счёта. - Reset = якорь ledger. Сброс пишет снова
QuotaReset(kind, at), ограничивающий катящееся окно — история использования/расхода никогда не стирается и не обнуляется. - Барьер на уровне службы, pre-turn — НЕ middleware авторизации (контролю нужны данные домена, не форма запроса). Барьеры стартуют до первого LLM-вызова в оркестраторе и до ingest на путях загрузки.
- Fail-open при незаданных квотах. Цепочка разрешения заканчивается на «без ограничений», когда ничего не настроено (D-08).
- Математика денег = Decimal→cents. ВСЯ арифметика расхода проходит через
Prisma.Decimalи приземляется целыми центами — никогда float (costQuotaService.ts, доктрина COST-01).
Цепочка разрешения (D-08)
| Уровень | Источник | Примечания |
|---|---|---|
| 1 | Переопределение на пользователя | User.tokenQuotaLimit / User.storageQuotaGb / строки расхода на пользователя — через PUT /api/quota/:userId (admin) |
| 2 | Пресет установки | Ключи SystemConfig (ниже) — DB > ENV > по умолчанию |
| 3 | Без ограничений | Страж-значение "0" / "" / unset = не настроено → fail-open |
Ключи конфигурации (SystemConfig, редактируемые через Settings)
| Ключ | По умолчанию | Значение |
|---|---|---|
QUOTA_TOKEN_DEFAULT | страж unset | Потолок токенов установки для каждого пользователя без переопределения |
QUOTA_STORAGE_GB_DEFAULT | страж unset | Потолок хранилища установки (GB) |
QUOTA_COST_EUR_DEFAULT | страж unset ("0"/"" = без ограничений) | Потолок РАСХОДА установки — десятичная строка EUR |
QUOTA_COST_FALLBACK_INPUT_CENTS_PER_1M | встроенный ненулевой пол | Резервный тариф SC-4 для моделей БЕЗ цены — input, целые центы за 1M токенов |
QUOTA_COST_FALLBACK_OUTPUT_CENTS_PER_1M | встроенный ненулевой пол | Резервный тариф SC-4 для моделей БЕЗ цены — output |
Доктрина SC-4: модель без цены никогда не обходит квоту расхода — резервные тарифы применяются с warn-once (warnFallbackOnce): безценная модель никогда не может считаться бесплатной.
Квоты токенов (quotaService.ts)
resolveTokenQuota(userId)→{ limit, source }—limit: null= без ограничений;sourceговорит, какой уровень D-08 решил.- Катящееся окно стартует от последнего якоря
QuotaResetдля(userId, kind: "token")(или от эпохи, если никогда не сбрасывалось); использование в окне суммируется из ledgerWorkspaceTokenUsage. - Превышение квоты выбрасывает
QuotaError(status+payload) — полеquotaв payload = контракт, который читают frontend, widget и handler коннекторов. Нет middleware, чтобы обойти: барьер работает ВНУТРИ пути сервиса.
Квоты расхода (costQuotaService.ts, Phase 216) — hold-and-settle
Двухфазное резервирование:
- Reserve (до вызова LLM) —
reserveCostQuota(principal, estimateCents)едет ОДНОЙ интерактивной транзакцией: условныйupdateMany, чей предикат (spentCents + estimate <= limitCents) Postgres оценивает с row-lock, так что N конкурирующих барьеров SERIALIZАЦИя вместо всех, проскочивших на старом контроле (SC-2) + insertQuotaHoldв той же транзакции. - Settle (на сегменте использования) —
settleCostHoldзакрывает hold и возмещаетestimate − actual. Превышение в полёте (actual > estimate) = принятое окно — никакого kill текущего стрима. - В покое действует инвариант:
settledSpent + Σ(открытые lead) ≤ предел. - Источник цены на строку: FROZEN-снимок Phase 203, где есть; иначе резервные тарифы SC-4 (warn-once); строки в валюте вне доминантной всё равно оцениваются (никогда молча бесплатно, никогда исключено).
- Сброс —
resetCostQuotaпишет якорьQuota(kind "cost")и ПЕРЕСОБИРАЕТ баланс из ledger-суммы + открытых hold (D-02).
Квоты хранилища
checkStorageQuota / resolveStorageQuota обеспечивают переопределение на пользователя storageQuotaGb, пресет QUOTA_STORAGE_GB_DEFAULT или безлимит. Барьеры стартуют на путях загрузки до dispatch к collector.
Principal для виджетов
Анонимные посетители виджета — не пользователи — resolveWidgetQuotaPrincipal привязывает widget-трафик к principal организации виджета: потребление виджета весит на цепочке квоты организации, не на per-visitator-идентичностях.
Где стартуют барьеры (места потребления)
| Поверхность | Барьер | Файл |
|---|---|---|
| Чат (JWT, widget proxy, коннекторы — один orchestrator) | барьер токенов + расхода pre-turn / pre-LLM | packages/server/src/agent/orchestrator.ts |
| Загрузка документа | барьер хранилища pre-ingest | packages/server/src/routes/uploads.ts, routes/documents.ts |
| Свип сброса | часовой сброс квот токенов (pg-boss cron: initQuotaResetScheduler) | services/quotaResetJob.ts |
| Самостоятельные чтения | GET /api/quota/me, rollups, ручной сброс | routes/quota.ts |
Поток оператора
- Установка по умолчанию — UI Settings → ключи
QUOTA_*(илиPUT /api/system/settings). - Переопределение на пользователя —
PUT /api/quota/:userId(admin) или UI квоты. - Цены на пользователя — семейство COSTS (Phase 209:
/api/users/:userId/pricing, ключи own-provider под/api/me/provider-keys) питает тот же движок. - Ручной сброс в любой момент:
POST /api/quota/:userId/reset; часовой свип держит стационарное состояние.
Цены моделей (MODEL_COSTS)
Настройка: Settings → Providers → выбрать провайдера → действие модели pricing → расход за 1M-токен input/output (напр. 1.50 и 6.00) + валюта → Save. Формула: cost = tokens × rate, вычисляется server-side при каждой записи использования, со снимком, замороженным во время исполнения (поздняя смена тарифа никогда не переписывает историю). Валюты: USD, EUR, GBP, JPY, CNY, INR — итоги по валюте, без конверсии в v1. N/A = цена не настроена (облачные модели unset; UI показывает "N/A" — администратор должен настроить); $0.00 = явно бесплатно (локальные/Ollama-модели заполнены нулём при миграции; переопределение возможно). Пределы v1: без конверсии между валютами; расход только главной итерации (под-конвейеры tool/MCP/synthesis исключены); расход доступен на событии done (не в середине стрима).
Справочник endpoints
Полная таблица живёт в API reference → Costs, quotas, and per-user model policy.
Смотри также
- Scaling — поведение квот при многопоточном развёртывании
Это руководство описывает операционную поверхность квот и расходов; техническая глубина живёт в статье-близнеце:
Provenance
| Раздел | Источник |
|---|---|
| Инварианты / цепочка разрешения / ключи | docs/QUOTAS.md (core invariants, D-08, ключи — Phase 207/203) — F-QUOTA-1 |
| Квоты расхода / hold-and-settle | docs/QUOTAS.md (§ Cost quotas — Phase 216) + costQuotaService.ts |
| Цены моделей | docs/MODEL_COSTS.md (Phase 203/209/216) |
| Места барьеров | docs/QUOTAS.md (§ Where the gates fire) |
---
Справочник оператора — Simmetric Chat v0.28.1 (документы обновлены 2026-10-02, SHA переписи `aca5b8ff`).