Documentation
¶
Overview ¶
Package orgusage meters per-org (tenant) monthly run launches and LLM spend, and enforces the launch-time caps. It is the billing source of truth in cloud mode: Prometheus counters stay global (no tenant labels — cardinality discipline), while these Mongo-backed counters answer "how much did this org consume this month".
The shape deliberately mirrors pkg/webhooks' Counter (same CAS strategy, same month-bucketed document ids) so operators reasoning about one quota system can reason about the other.
Index ¶
- Constants
- func CostToMillis(usd float64) int64
- func EnsureSchema(ctx context.Context, db *mongo.Database) error
- type Counter
- type DenyReason
- type MemoryCounter
- func (c *MemoryCounter) AddSpend(_ context.Context, tenantID string, when time.Time, costUSD float64, ...) error
- func (c *MemoryCounter) AllowRun(_ context.Context, tenantID string, when time.Time, maxRuns int, ...) (DenyReason, error)
- func (c *MemoryCounter) ReleaseRun(_ context.Context, tenantID string, when time.Time) error
- func (c *MemoryCounter) Usage(_ context.Context, tenantID string, when time.Time) (MonthlyUsage, error)
- type MongoCounter
- func (c *MongoCounter) AddSpend(ctx context.Context, tenantID string, when time.Time, costUSD float64, ...) error
- func (c *MongoCounter) AllowRun(ctx context.Context, tenantID string, when time.Time, maxRuns int, ...) (DenyReason, error)
- func (c *MongoCounter) ReleaseRun(ctx context.Context, tenantID string, when time.Time) error
- func (c *MongoCounter) Usage(ctx context.Context, tenantID string, when time.Time) (MonthlyUsage, error)
- type MonthlyUsage
Constants ¶
const RetentionDays = 400
RetentionDays bounds how long monthly usage documents are retained (Mongo TTL). 400 days keeps a full year of history plus margin for an annual billing cycle.
Variables ¶
This section is empty.
Functions ¶
func CostToMillis ¶
CostToMillis converts a USD amount to integer thousandths so the Mongo $inc stays integral (float $inc would accumulate drift) and the launch gate can express Team.MonthlyCostCapUSD in the counter's native unit.
Types ¶
type Counter ¶
type Counter interface {
// AllowRun atomically increments the month's launched-run counter
// and checks BOTH launch-time caps against the post-increment
// document in one round trip: maxRuns on the run count and
// maxCostMillis on the accumulated spend (each 0 = no cap; with no
// caps the increment still happens — that is the metering). A
// denied call rolls the increment back and reports which cap hit.
// The cost check is a soft cap by nature (a run's future spend is
// unknowable) — in-flight runs finish, new launches are denied.
AllowRun(ctx context.Context, tenantID string, when time.Time, maxRuns int, maxCostMillis int64) (DenyReason, error)
// AddSpend accumulates post-hoc LLM cost/token usage for the
// month. Never gates — AllowRun enforces the cap pre-launch.
AddSpend(ctx context.Context, tenantID string, when time.Time, costUSD float64, inputTokens, outputTokens int64) error
// ReleaseRun undoes one AllowRun admission whose launch was
// ultimately abandoned without any run being created (e.g. the
// loser of two concurrent duplicate webhook deliveries). Decrements
// the month's run counter; a missing month document is a no-op.
ReleaseRun(ctx context.Context, tenantID string, when time.Time) error
// Usage returns the month's counters for the org. A month with no
// activity returns the zero value (Month still filled).
Usage(ctx context.Context, tenantID string, when time.Time) (MonthlyUsage, error)
}
Counter is the per-org monthly metering + enforcement surface. Implementations: MongoCounter (production, atomic CAS) and MemoryCounter (tests/local). Keep semantics in lock-step.
type DenyReason ¶
type DenyReason string
DenyReason qualifies an AllowRun refusal so the launch gate can map it onto the right stable denial token.
const ( // DenyNone — the launch is allowed (and metered). DenyNone DenyReason = "" // DenyRuns — the monthly run quota is exhausted. DenyRuns DenyReason = "runs" // DenyCost — the month's accumulated LLM spend has reached the cap. DenyCost DenyReason = "cost" )
type MemoryCounter ¶
type MemoryCounter struct {
// contains filtered or unexported fields
}
MemoryCounter is the in-process Counter for tests and local mode. Keep its semantics in lock-step with MongoCounter.
func NewMemoryCounter ¶
func NewMemoryCounter() *MemoryCounter
func (*MemoryCounter) AllowRun ¶
func (c *MemoryCounter) AllowRun(_ context.Context, tenantID string, when time.Time, maxRuns int, maxCostMillis int64) (DenyReason, error)
func (*MemoryCounter) ReleaseRun ¶ added in v0.40.0
func (*MemoryCounter) Usage ¶
func (c *MemoryCounter) Usage(_ context.Context, tenantID string, when time.Time) (MonthlyUsage, error)
type MongoCounter ¶
type MongoCounter struct {
// contains filtered or unexported fields
}
MongoCounter is the production Counter. One document per (org, month); all increments go through findOneAndUpdate / $inc so the allow/deny decision is atomic per call (same CAS strategy as webhooks.MongoCounter).
func NewMongoCounter ¶
func NewMongoCounter(db *mongo.Database) *MongoCounter
func (*MongoCounter) AllowRun ¶
func (c *MongoCounter) AllowRun(ctx context.Context, tenantID string, when time.Time, maxRuns int, maxCostMillis int64) (DenyReason, error)
func (*MongoCounter) ReleaseRun ¶ added in v0.40.0
ReleaseRun mirrors the deny-path rollback in AllowRun: one $inc -1 on the month's run counter, no upsert (a TTL-evicted or never-created month document stays absent — decrementing a fresh doc below zero would corrupt the metering).
func (*MongoCounter) Usage ¶
func (c *MongoCounter) Usage(ctx context.Context, tenantID string, when time.Time) (MonthlyUsage, error)
type MonthlyUsage ¶
type MonthlyUsage struct {
// Month is the UTC bucket key, e.g. "2026-06".
Month string `json:"month"`
// Runs counts run launches accepted this month (REST + webhook +
// resume all consume the same budget — a resume re-enters the
// engine and spends like a launch).
Runs int `json:"runs"`
// CostUSD is the metered LLM spend accumulated by runners. Claw
// (in-process) nodes are metered precisely; delegate backends
// (claude_code) report tokens without a price table, so their
// cost contribution is zero — treat this as a floor, not an
// exact invoice.
CostUSD float64 `json:"cost_usd"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
}
MonthlyUsage is the read view of one org's current-month counters.