usage

package
v1.786.173 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 10, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Analytics entitlement contract.

This file is the ONE place cloud maps a Hanzo plan to the access that gates the RICH per-org usage-analytics surface. It does NOT introduce a second datastore and does NOT touch the metering WRITE path (the hanzoai/ai router still writes every AI call into the ONE global hanzo.cloud_usage ledger; the org column is the row-level tenant filter). It gates who may READ the rich lens over that ledger.

The split it enforces:

  • BASIC own-org usage (GET /v1/usage/summary) is ALWAYS allowed — every tenant can read its own footprint. Never gated here.
  • RICH per-org analytics/insights (retention window, exports, per-provider/BYO/ fee breakdowns — GET /v1/usage/analytics) is a PAID entitlement, gated here.

The values are NOT hardcoded: they are resolved from the @hanzo/plans catalog (the single source of truth) via the canonical `analytics.*` entitlement vocabulary, so the numbers live in exactly one place and are read, never duplicated.

Contract (keys to add to hanzoai/plans entitlements.schema.json):

analytics.datastore       boolean  access to the rich per-org analytics surface
analytics.retention_days  int      how far back the tenant may query
analytics.export          boolean  data export allowed

FOLLOW-UP (documented, coordinated — not built here). Per-request ENFORCEMENT needs the caller's effective plan id (org -> plan), which is a subscription lookup owned by the billing plane (commerce /v1/billing/subscriptions); the gateway principal carries org/project but NO plan claim today, and no org->plan resolver exists in clients/plan or clients/billing yet. Until it lands, ResolveAnalyticsAccess takes an explicit plan id and GET /v1/usage/analytics/access echoes any plan's access so dashboards self-config against the live catalog, and GET /v1/usage/analytics reads ?plan= for its gate. Wiring the resolver into the handler (swap the ?plan= line for the resolved caller-org plan) is the rollout step — the gate logic is unchanged by it.

Pure core of the usage summary: the response shape, the ledger→category mapper, the spend roll-up + gap-filled series assembler, and the ClickHouse value coercers. Everything here is I/O-free so the tests drive it with plain structs and mock rows — no commerce, no ClickHouse. The handler (usage.go) is the thin orchestration that fetches and calls these.

Package usage mounts the Hanzo Cloud UNIFIED USAGE surface (GET /v1/usage/summary): one org-scoped roll-up answering "what am I running and what does it cost" — the flagship footprint view. It is a native-Go composition over the two canonical shared cost sources, and exists because NO single endpoint aggregates this today:

  • Spend (the genuinely-missing categorized cost roll-up): the commerce ledger — usage-rollup (authoritative month-to-date consumed + prepaid wallet) plus the raw transaction ledger, rolled up server-side into spend-by-category over time. Every metered resource (GPU, machine-hours, LLM tokens, datastore footprint) debits this ONE ledger with a category tag, so it is already the unified cost source — this endpoint is the categorizing lens over it.
  • LLM usage totals: hanzo.cloud_usage, the per-org warehouse ledger (the same table /v1/analytics/* and the o11y board read). Totals only here — the per-model / timeseries detail stays at /v1/analytics/*.

The console Usage view composes THIS (cost roll-up + LLM totals) with the existing org-scoped inventory endpoints (/v1/machines, /v1/gpus, /v1/agents, provisioning lists) for the per-kind counts — one screen, this one authoritative money source.

TENANT ISOLATION (the bar). The org is the VALIDATED IAM owner claim (principal.Tenant — the trusted X-Org-Id the identity middleware minted from the caller's verified bearer, HIP-0026; NEVER a client header) AND a validated principal is required (c.User() set only for a verified bearer). The commerce subject is pinned server-side to that org; the warehouse query binds it POSITIONALLY. A caller can only ever read its OWN org. Fail-closed: no principal → 401. Every source degrades independently to honest zeros (source markers say which answered) — a partial deploy never fabricates spend or usage.

Registered as "usagesvc" (NOT "usage") + order 131: the name diverges from the /v1/usage route so serve.go's generic GET /v1/<name>/health parks at /v1/usagesvc/health and never shadows the summary. Order 131 binds /v1/usage/* before the ai subsystem's /v1/* catch-all (150).

Index

Constants

This section is empty.

Variables

View Source
var FreeAnalyticsAccess = AnalyticsAccess{Datastore: false, RetentionDays: 7, Export: false}

FreeAnalyticsAccess is the fail-closed floor: basic own-org usage only — no rich datastore surface, a 7-day query window, no export. It is applied whenever no plan can be resolved (unknown plan, or the catalog is unreachable), so a catalog outage degrades to exactly Free — never above.

Functions

func Mount

func Mount(app *zip.App, deps cloud.Deps) error

Mount wires the usage surface onto app per HIP-0106.

Types

type AnalyticsAccess added in v1.786.146

type AnalyticsAccess struct {
	Datastore     bool // analytics.datastore
	RetentionDays int  // analytics.retention_days
	Export        bool // analytics.export
}

AnalyticsAccess is the resolved analytics entitlement for one caller. Datastore grants the rich per-org analytics/datastore surface; RetentionDays caps how far back the tenant may query; Export allows data export.

func AnalyticsAccessFromEntitlements added in v1.786.146

func AnalyticsAccessFromEntitlements(ent map[string]any) AnalyticsAccess

AnalyticsAccessFromEntitlements maps a resolved `analytics.*` entitlement block onto AnalyticsAccess. Pure and total: any key absent from the block keeps the Free-floor value, so a partial catalog can only ever NARROW access (never widen past the floor). Wrong-typed values are ignored (kept at floor).

func ResolveAnalyticsAccess added in v1.786.146

func ResolveAnalyticsAccess(ctx context.Context, planID string) (AnalyticsAccess, error)

ResolveAnalyticsAccess resolves analytics access for a plan id from the @hanzo/plans catalog (the single source of truth). An empty id short-circuits to FreeAnalyticsAccess (the floor) WITHOUT a catalog round-trip, so an unauthenticated or plan-less caller fails closed and CI stays catalog-independent. On any resolution error it returns FreeAnalyticsAccess together with the error, so callers fail closed while logging the degradation.

type AnalyticsView added in v1.786.146

type AnalyticsView struct {
	Scope         Scope             `json:"scope"`
	Plan          string            `json:"plan"`
	Range         string            `json:"range"`
	Start         string            `json:"start"`
	End           string            `json:"end"`
	RetentionDays int               `json:"retentionDays"`
	Export        bool              `json:"export"`
	Providers     ProviderBreakdown `json:"providers"`
}

AnalyticsView is the entitlement-gated rich read: the per-provider breakdown of the org's LLM usage over the retention-clamped window, plus the plan's retention + export decision so the console renders the right controls.

type CategorySpend

type CategorySpend struct {
	Category string `json:"category"`
	Cents    int64  `json:"cents"`
	Count    int64  `json:"count"`
}

CategorySpend is one row of the spend-by-category breakdown: a friendly category bucket, the total cents spent in the window, and how many ledger lines rolled up into it. Categories are DERIVED from the commerce ledger's own tags — never fabricated — so an unknown tag surfaces as its own honest bucket.

type LLM

type LLM struct {
	Available        bool   `json:"available"`
	Requests         int64  `json:"requests"`
	Tokens           int64  `json:"tokens"`
	PromptTokens     int64  `json:"promptTokens"`
	CompletionTokens int64  `json:"completionTokens"`
	CostCents        int64  `json:"costCents"`
	Models           int64  `json:"models"`
	Source           string `json:"source"`
}

LLM is the org's LLM usage totals from the warehouse ledger. Available=false when the datastore is not connected (honest zeros). The detailed per-model / timeseries breakdown lives at /v1/analytics/*; this is the KPI-band total.

type ProviderBreakdown added in v1.786.146

type ProviderBreakdown struct {
	Available bool          `json:"available"`
	Items     []ProviderRow `json:"items"`
	Source    string        `json:"source"`
}

ProviderBreakdown is the per-provider roll-up. Available=false is honest-empty (the datastore is not connected) — never fabricated, exactly like the summary's LLM block.

type ProviderRow added in v1.786.146

type ProviderRow struct {
	Provider  string `json:"provider"`
	Requests  int64  `json:"requests"`
	Tokens    int64  `json:"tokens"`
	CostCents int64  `json:"costCents"`
}

ProviderRow is one provider's windowed totals. BYO/fee/account columns land with the hanzoai/ai metering-write branch; see buildAnalyticsBlock for the one-line projection extension.

type Scope

type Scope struct {
	Org string `json:"org"`
}

type Sources

type Sources struct {
	Commerce  bool `json:"commerce"`
	Warehouse bool `json:"warehouse"`
}

Sources reports which upstreams actually answered, so the console can badge a board as "connected" vs "no data yet" honestly instead of showing a fabricated zero as if it were real.

type Spend

type Spend struct {
	Available      bool            `json:"available"`
	TotalCents     int64           `json:"totalCents"`
	MTDCents       int64           `json:"mtdCents"`
	OverageCents   int64           `json:"overageCents"`
	BalanceCents   int64           `json:"balanceCents"`
	AvailableCents int64           `json:"availableCents"`
	ByCategory     []CategorySpend `json:"byCategory"`
	Series         []SpendPoint    `json:"series"`
	Source         string          `json:"source"`
}

Spend is the cost roll-up: the genuinely-missing aggregation. TotalCents is the windowed consumption (self-consistent with ByCategory + Series); MTDCents is commerce's authoritative month-to-date figure; Balance/Available are the prepaid wallet the gateway debits. Available=false means commerce was unconfigured or unreachable — honest zeros, never fabricated spend.

type SpendPoint

type SpendPoint struct {
	T     string `json:"t"` // RFC3339 bucket start (UTC)
	Cents int64  `json:"cents"`
}

SpendPoint is one time bucket of consumption (usage/withdrawal cents).

type Summary

type Summary struct {
	Range    string  `json:"range"`
	Start    string  `json:"start"`
	End      string  `json:"end"`
	Interval string  `json:"interval"`
	Scope    Scope   `json:"scope"`
	Spend    Spend   `json:"spend"`
	LLM      LLM     `json:"llm"`
	Sources  Sources `json:"sources"`
}

Summary is the whole org-scoped usage footprint roll-up: the cost roll-up (spend) + the LLM usage totals, over one window, for one org.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL