mirror of
https://github.com/hanzoai/commerce.git
synced 2026-10-30 15:08:42 +00:00
IsSuperAdmin folded the case of the home org and fell back to the owner claim, the org of the application that minted the token, and the money bits were granted on X-User-Owner == admin. Cloud writes X-User-Owner for an admin-org application and an admin-org person's API key as well as for a person, so either could mint money and edit the catalog here while being nobody in the rest of the same process. Every gate now reads X-User-IsAdmin, which the edge strips and writes from authz.Claims.Sudo alone; the standalone EdgeAuth decides it with that predicate over the verified token's signed membership set. X-User-IsAdmin no longer doubles as the org-admin bit, which is X-User-IsOrgAdmin. No machine is SuperAdmin. Global admin is SuperAdmin wherever it named this predicate.
133 lines
6.3 KiB
Go
133 lines
6.3 KiB
Go
// Package metrics is the platform SaaS-operations god-view: the whole business's
|
|
// money, computed IN commerce (the system of record for subscriptions + the usage
|
|
// ledger) so admin.hanzo.ai only renders — never re-aggregates client-side and
|
|
// never calls Stripe directly. It is a cross-tenant aggregate (MRR/ARR, plan mix,
|
|
// usage tops, top customers), so every handler is gated on the STRICT
|
|
// middleware.RequirePlatformAdmin predicate (SuperAdmin or the trusted internal
|
|
// service token) — the same gate api/costs uses. Org-level admins are refused.
|
|
//
|
|
// Sources of truth (all local to commerce; nothing fabricated):
|
|
// - Subscriptions: models/subscription — the internal recurring-billing ledger
|
|
// (ProviderType "internal"/"bundle"), walked across every org namespace.
|
|
// - Plan categories/prices: the embedded @hanzo/plans catalog (api/billing),
|
|
// joined by plan slug.
|
|
// - Usage / pay-as-you-go spend: models/transaction rows tagged "api-usage".
|
|
//
|
|
// Where a figure is genuinely not instrumented (e.g. plan-change events for
|
|
// upgrade/downgrade counts), it is reported as null with an honest note in Gaps —
|
|
// never a fabricated number.
|
|
package metrics
|
|
|
|
// SaaSMetrics is the single snapshot GET /v1/commerce/metrics/saas returns: the
|
|
// four operator panels (revenue, subscriptions, usage, customers) folded from ONE
|
|
// cross-org walk. Money is USD cents end to end; AsOf is when the walk ran.
|
|
type SaaSMetrics struct {
|
|
AsOf string `json:"asOf"`
|
|
Currency string `json:"currency"`
|
|
Window string `json:"window"` // the usage/new-churn window, e.g. "30d"
|
|
Revenue RevenueMetrics `json:"revenue"`
|
|
Subs SubscriptionMetrics `json:"subscriptions"`
|
|
Usage UsageMetrics `json:"usage"`
|
|
// Customers are the top orgs by MRR + windowed usage, capped at the request limit.
|
|
Customers []CustomerRow `json:"customers"`
|
|
// Orgs is the total number of tenant organizations walked (denominator context).
|
|
Orgs int `json:"orgs"`
|
|
// Gaps are honest "not instrumented yet" notes for figures we cannot source.
|
|
Gaps []string `json:"gaps"`
|
|
}
|
|
|
|
// RevenueMetrics is the recurring-revenue headline: run-rate (MRR/ARR) from ACTIVE
|
|
// subscriptions plus the new/churned movement inside the window.
|
|
type RevenueMetrics struct {
|
|
MRRCents int64 `json:"mrrCents"`
|
|
ARRCents int64 `json:"arrCents"`
|
|
ActiveSubscriptions int `json:"activeSubscriptions"`
|
|
PayingCustomers int `json:"payingCustomers"`
|
|
Trials int `json:"trials"`
|
|
NewMRRCents int64 `json:"newMrrCents"`
|
|
ChurnedMRRCents int64 `json:"churnedMrrCents"`
|
|
NetNewMRRCents int64 `json:"netNewMrrCents"`
|
|
ByCategory []CategoryMRR `json:"byCategory"`
|
|
}
|
|
|
|
// CategoryMRR is one plan-category bucket of run-rate MRR (e.g. world, personal,
|
|
// team, social) — the category comes from the embedded @hanzo/plans catalog.
|
|
type CategoryMRR struct {
|
|
Category string `json:"category"`
|
|
MRRCents int64 `json:"mrrCents"`
|
|
Subscriptions int `json:"subscriptions"`
|
|
}
|
|
|
|
// SubscriptionMetrics is the subscription operations panel: the per-plan mix, the
|
|
// active trial count, the new/canceled counts inside the window, and a recent
|
|
// events feed synthesized from real subscription create/cancel timestamps.
|
|
type SubscriptionMetrics struct {
|
|
ByPlan []PlanBreakdown `json:"byPlan"`
|
|
TrialsActive int `json:"trialsActive"`
|
|
New int `json:"new"`
|
|
Canceled int `json:"canceled"`
|
|
// Upgrades/Downgrades are nil: plan-change events are NOT recorded (see Gaps).
|
|
Upgrades *int `json:"upgrades"`
|
|
Downgrades *int `json:"downgrades"`
|
|
Recent []SubEvent `json:"recent"`
|
|
}
|
|
|
|
// PlanBreakdown is one plan's active/trialing counts, seats, and MRR contribution.
|
|
type PlanBreakdown struct {
|
|
Plan string `json:"plan"` // plan slug
|
|
Name string `json:"name"`
|
|
Category string `json:"category"`
|
|
Active int `json:"active"`
|
|
Trialing int `json:"trialing"`
|
|
Seats int `json:"seats"`
|
|
MRRCents int64 `json:"mrrCents"`
|
|
}
|
|
|
|
// SubEvent is one entry in the recent-subscription feed. Type is "created" or
|
|
// "canceled" (the two movements we can source from timestamps); MRRDeltaCents is
|
|
// the positive (new) or negative (churn) monthly contribution.
|
|
type SubEvent struct {
|
|
At string `json:"at"`
|
|
Org string `json:"org"`
|
|
Type string `json:"type"`
|
|
Plan string `json:"plan"`
|
|
Category string `json:"category"`
|
|
MRRDeltaCents int64 `json:"mrrDeltaCents"`
|
|
}
|
|
|
|
// UsageMetrics is the metered / pay-as-you-go REVENUE headline: the billed usage
|
|
// (what customers actually paid on the wallet ledger — the api-usage withdrawals,
|
|
// NOT metered COGS) and the request count in the window. It is the money-domain
|
|
// complement to MRR: total revenue = recurring (MRR) + metered (this).
|
|
//
|
|
// PER-MODEL tokens, per-org AI-spend ranking, latency and error rate are
|
|
// deliberately NOT here. The canonical fleet LLM-observability aggregate is the
|
|
// cloud god-view GET /v1/admin/o11y (Datastore hanzo.cloud_usage + signoz_traces),
|
|
// already rendered by the console "Fleet Observability" board. The SaaS module
|
|
// consumes THAT for its AI panel rather than re-deriving per-model numbers from
|
|
// commerce's noindex ledger — one aggregate, no fork, no risk of two disagreeing
|
|
// per-model tables. What o11y cannot source (per-MODEL latency/error) is called out
|
|
// honestly in Gaps rather than fabricated here.
|
|
type UsageMetrics struct {
|
|
Instrumented bool `json:"instrumented"`
|
|
WindowUsageCents int64 `json:"windowUsageCents"`
|
|
Requests int64 `json:"requests"`
|
|
// UntaggedRequests counts api-usage rows with no model tag — a data-quality
|
|
// signal for the o11y attribution, reported honestly.
|
|
UntaggedRequests int64 `json:"untaggedRequests"`
|
|
}
|
|
|
|
// CustomerRow is one top customer: its tier, status, run-rate MRR, windowed usage,
|
|
// seats, and the earliest active-subscription start. The console links Org to the
|
|
// existing fleet-customer drill-in rather than duplicating it.
|
|
type CustomerRow struct {
|
|
Org string `json:"org"`
|
|
Plan string `json:"plan"`
|
|
Category string `json:"category"`
|
|
Status string `json:"status"`
|
|
MRRCents int64 `json:"mrrCents"`
|
|
UsageCents int64 `json:"usageCents"`
|
|
Seats int `json:"seats"`
|
|
Since string `json:"since,omitempty"`
|
|
}
|