Quotas e custos — os limites de uso e de despesa
O motor de quota cobre três géneros: a quota de tokens e a quota de armazenamento (Phase 207) mais a quota de custo (Phase 216, hold-and-settle sobre a matemática do dinheiro real). As quotas são a ferramenta do operador para os limites de uso em instala…
O motor de quota cobre três géneros: a quota de tokens e a quota de armazenamento (Phase 207) mais a quota de custo (Phase 216, hold-and-settle sobre a matemática do dinheiro real). As quotas são a ferramenta do operador para os limites de uso em instalações multi-utilizador; vinculam cada utilizador com uma quota configurada — administradores incluídos — e uma quota por configurar = ilimitado (fail-open: zero mudança de comportamento para as instalações existentes).
Funcionalidade Community (a ferramenta do operador). Fontes:docs/QUOTAS.md(Phase 207 + 216),docs/MODEL_COSTS.md,docs/API.md(a secção Costs, quotas, and per-user model policy). Linhas de proveniência:FACT-CENSUS.mdF-QUOTA-1.
As invariantes de fundamento
- Nenhum segundo contador. A verdade do consumo (tokens/custo) fica no ledger existente
WorkspaceTokenUsage(packages/server/src/services/quotaService.ts).QuotaBalance/QuotaHold/QuotaReset= estado derivado e ancorado, nunca uma tabela de contagem. - Reset = a âncora do ledger. Um reset escreve de novo
QuotaReset(kind, at)que delimita a janela rolante — o histórico de uso/custo nunca é apagado nem zerado. - A barreira ao nível do serviço, pre-turn — NÃO o middleware de auth (o controlo precisa de dados do domínio, não da forma do pedido). As barreiras arrancam antes da primeira chamada LLM no orchestrator e antes da ingestão nas vias de carregamento.
- Fail-open em quotas por configurar. A cadeia de resolução termina em "ilimitado" quando nada está configurado (D-08).
- A matemática do dinheiro é Decimal→cents. TODA a aritmética de custos passa por
Prisma.Decimale aterra em centavos inteiros — nunca float (costQuotaService.ts, a doutrina COST-01).
A cadeia de resolução (D-08)
| Nível | Fonte | Notas |
|---|---|---|
| 1 | O override por utilizador | User.tokenQuotaLimit / User.storageQuotaGb / as linhas de custo por utilizador — via PUT /api/quota/:userId (admin) |
| 2 | O preset da instalação | As chaves SystemConfig (abaixo) — DB > ENV > predefinição |
| 3 | Ilimitado | O valor sentinela "0" / "" / unset = não configurado → fail-open |
As chaves de configuração (SystemConfig, editáveis via Settings)
| Chave | Predefinição | Significado |
|---|---|---|
QUOTA_TOKEN_DEFAULT | o sentinela unset | O limite de tokens da instalação para cada utilizador sem override |
QUOTA_STORAGE_GB_DEFAULT | o sentinela unset | O limite de armazenamento da instalação (GB) |
QUOTA_COST_EUR_DEFAULT | o sentinela unset ("0"/"" = ilimitado) | O limite de CUSTO da instalação — cadeia decimal EUR |
QUOTA_COST_FALLBACK_INPUT_CENTS_PER_1M | um piso integrado não nulo | A tarifa de recurso SC-4 para modelos SEM preço — input, centavos inteiros por 1M tokens |
QUOTA_COST_FALLBACK_OUTPUT_CENTS_PER_1M | um piso integrado não nulo | A tarifa de recurso SC-4 para modelos SEM preço — output |
A doutrina SC-4: um modelo sem preço nunca contorna a quota de custo — as tarifas de recurso aplicam-se com warn-once (warnFallbackOnce): um modelo sem preço nunca pode resultar grátis.
As quotas de tokens (quotaService.ts)
resolveTokenQuota(userId)→{ limit, source }—limit: null= ilimitado;sourcediz que nível da D-08 decidiu.- A janela rolante parte da última âncora
QuotaResetpara(userId, kind: "token")(ou da época se nunca reiniciado); o uso na janela soma-se a partir do ledgerWorkspaceTokenUsage. - Ultrapassar a quota lança
QuotaError(status+payload) — o campoquotado payload é o contrato que frontend, widget e os handlers dos conectors leem. Não há middleware que contorne: a barreira corre DENTRO da via do serviço.
As quotas de custo (costQuotaService.ts, Phase 216) — hold-and-settle
A reserva em duas fases:
- Reserve (antes da chamada LLM) —
reserveCostQuota(principal, estimateCents)executa UMA transação interativa: umupdateManycondicional cujo predicado (spentCents + estimate <= limitCents) o Postgres avalia com row-lock, de modo que N barreiras concorrentes se SERIALIZAM em vez de todas passar sobre um controlo velho (SC-2) + o insert deQuotaHoldna mesma transação. - Settle (ao segmento de uso) —
settleCostHoldencerra o hold e reembolsaestimate − actual. O overshoot em voo (actual > estimate) = a janela aceite — nenhum kill de um stream em curso. - Em repouso vale a invariante:
settledSpent + Σ(lead abertas) ≤ limite. - A fonte do preço por linha: o snapshot FROZEN da Phase 203 onde existe; senão as tarifas de recurso SC-4 (warn-once); as linhas em moeda fora da dominante ficam mesmo assim precificadas (nunca silenciosamente grátis, nunca excluídas).
- Reset —
resetCostQuotaescreve a âncoraQuota(kind "cost")e RECONSTRÓI o saldo a partir de ledger-soma + holds abertos (D-02).
As quotas de armazenamento
checkStorageQuota / resolveStorageQuota aplicam o override por utilizador storageQuotaGb, o preset QUOTA_STORAGE_GB_DEFAULT ou o ilimitado. As barreiras arrancam nas vias de carregamento antes do dispatch ao collector.
O principal para os widgets
Os visitantes anónimos do widget não são utilizadores — resolveWidgetQuotaPrincipal liga o tráfego widget ao principal da organização do widget: o consumo do widget pesa na cadeia de quotas da organização, não em identidades por visitante.
Onde as barreiras arrancam (os sítios de consumo)
| Superfície | Barreira | Ficheiro |
|---|---|---|
| Chat (JWT, widget proxy, conectors — um orchestrator) | barreira de tokens + custo pre-turn / pre-LLM | packages/server/src/agent/orchestrator.ts |
| Carregamento de documento | a barreira de armazenamento pre-ingest | packages/server/src/routes/uploads.ts, routes/documents.ts |
| A varredura de reset | o reset horário das quotas de tokens (pg-boss cron: initQuotaResetScheduler) | services/quotaResetJob.ts |
| As leituras self-service | GET /api/quota/me, rollups, reset manual | routes/quota.ts |
O fluxo do operador
- A predefinição da instalação — a UI de Settings → as chaves
QUOTA_*(ouPUT /api/system/settings). - O override por utilizador —
PUT /api/quota/:userId(admin) ou a UI de quota. - Os preços por utilizador — a família COSTS (Phase 209:
/api/users/:userId/pricing, as chaves de fornecedores próprios sob/api/me/provider-keys) alimenta o mesmo motor. - O reset manual a qualquer momento:
POST /api/quota/:userId/reset; a varredura horária sustenta o estado estacionário.
Os preços dos modelos (MODEL_COSTS)
Configuração: Settings → Providers → escolher o fornecedor → a ação pricing do modelo → o custo por-1M-token input/output (p. ex. 1.50 e 6.00) + a moeda → Save. A fórmula: cost = tokens × rate, calculada server-side em cada escrita de uso, com snapshot congelado ao tempo de execução (uma mudança posterior da tarifa nunca reescreve o histórico). Moedas: USD, EUR, GBP, JPY, CNY, INR — totais por moeda, nenhuma conversão na v1. N/A = preço por configurar (os modelos cloud unset; a UI mostra "N/A" — a administração tem de configurar); $0.00 = explicitamente grátis (os modelos locais/Ollama foram preenchidos a 0 na migração; override possível). Os limites da v1: nenhuma conversão entre moedas; só o custo da iteração principal (as sub-pipelines tool/MCP/synthesis excluídas); o custo fica disponível no evento done (não a meio do stream).
A referência de endpoints
A tabela completa vive em API reference → Costs, quotas, and per-user model policy.
Ver também
- Scaling — o comportamento das quotas sob implantação multi-instância
Este guia cobre a superfície operacional das quotas e dos custos; a profundidade técnica vive no post gémeo:
Provenance
| Secção | Fonte |
|---|---|
| Invariantes / a cadeia de resolução / as chaves | docs/QUOTAS.md (core invariants, D-08, as chaves — Phase 207/203) — F-QUOTA-1 |
| As quotas de custo / hold-and-settle | docs/QUOTAS.md (§ Cost quotas — Phase 216) + costQuotaService.ts |
| Os preços dos modelos | docs/MODEL_COSTS.md (Phase 203/209/216) |
| Os sítios das barreiras | docs/QUOTAS.md (§ Where the gates fire) |
---
Referência operativa — Simmetric Chat v0.28.1 (documentos atualizados a 2026-10-02, SHA do census `aca5b8ff`).