leaderboard

package
v1.801.307 Latest Latest
Warning

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

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

Documentation

Overview

GET /v1/usage/activity — the per-day contribution series (GitHub-style heatmap + timeline) for ONE authorized subject.

subject=user    the caller's OWN activity (default); another user only for an org
                admin / SuperAdmin, and only a user WITHIN the caller's org
subject=org     the caller's OWN org (default); another org only for a SuperAdmin
subject=project honest-empty — per-project attribution is not in the usage ledger
                yet (documented gap; lights up when cloud_usage gains a project
                column, a one-line projection change)
from,to         optional day range (default 90d; clamped to 366d)

Authorization is resolved SERVER-SIDE from the validated principal; a caller can never widen scope past what they're entitled to. org is always the leading bound predicate in the query.

POST /v1/usage/rollup/backfill — the DEPLOY-GATED, run-ONCE seed of the derived rollup from pre-MV ledger history. SuperAdmin only.

The incremental MV captures rows inserted AFTER its creation; this seeds everything before. Because SummingMergeTree accumulates, a second unguarded run would double a day — so it refuses when the rollup is already non-empty unless ?force=true. Pass ?before=<RFC3339> to bound the seed (default now); use the MV-creation instant so the seed and the live MV never overlap.

GET /v1/usage/leaderboard — the ranked board.

scope=personal  caller's rank + the top users of the caller's OWN org, identities
                anonymized except self + opted-in peers (the "as a participant" view)
scope=org       the caller's org board; identities NAMED only for an org admin /
                SuperAdmin (they may see their org's members), else anonymized
scope=global    the top ORGS; every org NAMED for a SuperAdmin, else only the orgs
                that opted into the public board — plus the caller's OWN org rank
metric=tokens|requests|cost   period=day|week|month|all   limit (clamped 1..100)

Tenant isolation: org is principal.Org (validated), bound positionally in every query. Cross-org cost is SuperAdmin-only. Datastore down → honest-empty.

Package leaderboard mounts the Hanzo Cloud GAMIFIED usage analytics surface: AI usage leaderboards (top users / orgs) + a GitHub-style per-day contribution graph, over the datastore OLAP rollup (#43). It is a DERIVED, read-only lens over the ONE usage ledger (hanzo.cloud_usage) — it adds no metering path and double-counts nothing.

Surface (all /v1, NO /api/ prefix; org-scoped, fail-closed):

GET  /v1/usage/leaderboard   ranked top users (personal|org) or orgs (global)
GET  /v1/usage/activity      per-day series for a heatmap + timeline (authorized subject)
GET  /v1/usage/leaderboard/optin       the caller's opt-in + their org's opt-in
PUT  /v1/usage/leaderboard/optin        set the caller's OWN public-listing opt-in
PUT  /v1/usage/leaderboard/optin/org    set the ORG's public-board opt-in (org admin)
POST /v1/usage/rollup/backfill          seed the rollup from ledger history (SuperAdmin, once)

It co-owns the /v1/usage/* prefix with clients/usage (the cost footprint at /v1/usage/summary) — a DISTINCT concern (who leads + your activity graph) at its own paths, registered as a separate subsystem so it stays isolated. Its auto health route is /v1/leaderboard/health (the spec name).

TENANT ISOLATION (the bar). The org is the VALIDATED IAM owner claim (principal.Org — the trusted X-Org-Id the identity middleware minted from the verified bearer, HIP-0026; NEVER a client header) AND a validated principal is required. Every datastore read binds the org POSITIONALLY (never interpolated). A user board only ever contains the caller's own org's rows; an org board carries org-level aggregates only; cross-org detail is structurally impossible. Fail closed: no principal → 401; datastore not connected → honest-empty (available:false), never fabricated ranks.

PRIVACY (opt-in). Public listing is OPT-IN and PRIVATE by default: a user sees their OWN rank always, but is shown to others only after opting in with a chosen handle; an org appears on the cross-org global board only after an org admin opts it in. See view.go for the naming/anonymization policy.

The opt-in surface — PUBLIC-LISTING IS OPT-IN, PRIVATE BY DEFAULT.

GET /v1/usage/leaderboard/optin        the caller's own opt-in + their org's opt-in
PUT /v1/usage/leaderboard/optin         set the caller's OWN listing (self only)
PUT /v1/usage/leaderboard/optin/org     set the ORG's public-board listing (org admin)

A user writes ONLY their own preference (keyed by their validated ledger id); an org preference is writable only by an admin OF that org. Nothing here is secret.

The datastore rollup (#43): a DERIVED per-day pre-aggregation of hanzo.cloud_usage that makes leaderboard + activity reads cheap. It is NOT a second metering path — it is a rollup of the ONE ledger, so it can never double-count what cloud_usage already records.

ENGINE CHOICE — SummingMergeTree, not AggregatingMergeTree. Every rollup metric is a pure SUM (requests, tokens, cost); distinct-user / distinct-model counts fall out of the (org,user,model,day) grain at read time. SummingMergeTree is the simplest engine that does exactly "collapse rows with the same sort key by summing the rest", stores plain UInt64 (read with a normal sum(), no -Merge), and is the established house pattern (commerce.daily_sales_mv). AggregatingMergeTree would only earn its keep for non-sum aggregate states (uniq/quantile) — we have none.

INCREMENTAL MV. rollupMV is attached to hanzo.cloud_usage: every INSERT into the ledger fires it, pre-aggregating THAT block into the rollup. The MV SELECT is pure and TYPE-EXACT (toDate→Date, sum(UInt32)→UInt64, count()→UInt64, all matching the target columns), so it cannot fail on a valid ledger row — it never endangers the (fire-and-forget) metering write. It captures rows inserted AFTER its creation; pre-existing history is seeded ONCE by the deploy-gated backfill.

Pure, I/O-free core of the usage leaderboard + activity reads: the metric allowlist, the period→day-window resolver, the day literal, and the datastore SQL BUILDERS. Everything here is a pure function so the tests drive it with plain values — no datastore, no HTTP.

INJECTION SAFETY (the bar). The tenant key (organization) and every user- supplied value (subject id, day bounds, self-rank threshold) is ALWAYS a bound `?` parameter, appended to the args slice — NEVER string-interpolated into the SQL. The only tokens interpolated are (a) the metric column, taken from the closed `metricColumn` allowlist (a caller's `metric=` can only ever select one of three fixed column names, or be rejected), and (b) the LIMIT, a server- clamped int. This mirrors the proven house pattern (ai/object cloud_usage.go whereClause, clients/analytics query.go llmWhere): org bound positionally, the bucket/limit a closed enum / validated int. The builders return (sql, args) so a test can assert a hostile org slug or metric lands in args (or is rejected), never in the SQL string.

The opt-in preference store: the durable, OPT-IN-BY-DEFAULT-PRIVATE half of the leaderboard. Two tiny tenant-keyed tables in one Hanzo Base/SQLite file — the same eval/settings discipline (cek.Open, MaxOpenConns(1) to serialize writes against the file lock, a mandatory key predicate on every statement).

  • user_optin (user_id PK, org, handle, listed): a user opts THEMSELVES into public listing with a chosen handle. Default absent ⇒ NOT listed (private): the user still sees their own rank, but is never named to others.
  • org_optin (org PK, display, listed): an org admin opts the ORG into the public global board with a display name. Default absent ⇒ private.

Isolation: a user row is written only for the CALLER's own user_id; the listing read is org-scoped (`WHERE org=? AND listed=1`). An org row is written only by an admin of that org; the global-listing read is `WHERE listed=1` (org-level display only — no user data). No secret ever lands here.

Response shapes + the PURE naming / anonymization / value-coercion functions. I/O-free so the privacy policy is unit-tested directly with plain values.

PRIVACY MODEL (the product rule).

  • A leaderboard row carries an AGGREGATE metric + a display identity. The identity is revealed only when the viewer is authorized to see it:
  • self → always ("you")
  • opted-in peer → the handle THEY chose (opt-in store)
  • admin viewer → the member's username (org admin sees org members; SuperAdmin sees anyone) — the name half of user_id, no IAM round-trip, no cross-service call
  • everyone else → "Anonymous" (identity withheld; metric still shown)
  • Cross-ORG isolation is enforced upstream by the org-bound SQL: a user board only ever contains the caller's own org's rows; an org board carries only org-level aggregates (never a user identity). So a row can never carry ANOTHER tenant's user detail.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BackfillUsageRollup

func BackfillUsageRollup(ctx context.Context, before time.Time) error

BackfillUsageRollup seeds the rollup from ledger history with timestamp < before. It is the DEPLOY-GATED, run-ONCE step: because SummingMergeTree accumulates, a second unguarded run would double a day, so the handler guards on an empty rollup (or an explicit force). `before` should be the MV-creation instant (or now) so the seed and the live MV do not overlap.

func EnsureUsageRollup

func EnsureUsageRollup(ctx context.Context) error

EnsureUsageRollup creates the derived rollup table + the incremental MV if they do not exist (the base ledger first, since the MV reads it). Idempotent and latched: a transient datastore blip does not permanently poison later attempts. Every leaderboard/activity read calls it first — the same discipline as EnsureCloudUsageTable — so the feature self-provisions its rollup the first time it runs against a connected datastore.

func Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount wires the leaderboard surface onto app per HIP-0106 — one line over the generic subsystem entrypoint.

func Shutdown

func Shutdown(_ context.Context) error

Shutdown closes the opt-in store on SIGTERM (registered as the subsystem's Shutdown hook). Idempotent.

Types

type ActivityPoint

type ActivityPoint struct {
	Day       string `json:"day"` // "2006-01-02"
	Requests  int64  `json:"requests"`
	Tokens    int64  `json:"tokens"`
	CostCents int64  `json:"costCents"`
}

ActivityPoint is one day of a subject's usage — the atom of the contribution heatmap + timeline. CostCents populated only when the viewer may see spend.

type ActivityTotals

type ActivityTotals struct {
	Requests    int64 `json:"requests"`
	Tokens      int64 `json:"tokens"`
	CostCents   int64 `json:"costCents"`
	ActiveDays  int   `json:"activeDays"` // days with any usage
	MaxTokens   int64 `json:"maxTokens"`  // busiest day's tokens (heatmap intensity ceiling)
	MaxRequests int64 `json:"maxRequests"`
}

ActivityTotals are the window sums + heatmap-scaling hints.

type ActivityView

type ActivityView struct {
	Subject   string          `json:"subject"` // user|org|project
	ID        string          `json:"id"`      // resolved subject id (echoed)
	From      string          `json:"from"`
	To        string          `json:"to"`
	Days      []ActivityPoint `json:"days"`
	Totals    ActivityTotals  `json:"totals"`
	Available bool            `json:"available"`
	Source    string          `json:"source"`
	Note      string          `json:"note,omitempty"` // honest note (e.g. project attribution not in the ledger)
}

ActivityView is the per-day series for one authorized subject.

type LeaderboardRow

type LeaderboardRow struct {
	Rank      int    `json:"rank"`
	Handle    string `json:"handle"`
	Anonymous bool   `json:"anonymous"`
	Self      bool   `json:"self"`
	Requests  int64  `json:"requests"`
	Tokens    int64  `json:"tokens"`
	CostCents int64  `json:"costCents"`
	Metric    int64  `json:"metric"`
}

LeaderboardRow is one ranked subject (a user or an org). Handle is the display identity per the privacy model; Anonymous marks a withheld identity; Self marks the caller's own row. Requests/Tokens are non-sensitive volume aggregates; CostCents is populated ONLY when the viewer is authorized to see this subject's spend (self, admin, or an explicit cost board — see costVisible). Metric is the value the board is ranked by (for bar sizing on the client).

type LeaderboardView

type LeaderboardView struct {
	Scope     string           `json:"scope"`   // personal|org|global
	Subject   string           `json:"subject"` // user|org
	Metric    string           `json:"metric"`  // tokens|requests|cost
	Period    string           `json:"period"`  // day|week|month|all|custom
	Start     string           `json:"start"`   // "" for all
	End       string           `json:"end"`
	Rows      []LeaderboardRow `json:"rows"`
	Self      *SelfRank        `json:"self,omitempty"`
	Total     int64            `json:"total"` // ranked subjects in the window
	Available bool             `json:"available"`
	Source    string           `json:"source"`
}

LeaderboardView is the whole leaderboard response.

type SelfRank

type SelfRank struct {
	Ranked    bool   `json:"ranked"`
	Rank      int    `json:"rank"`
	OfTotal   int64  `json:"ofTotal"`
	Requests  int64  `json:"requests"`
	Tokens    int64  `json:"tokens"`
	CostCents int64  `json:"costCents"`
	Metric    int64  `json:"metric"`
	Handle    string `json:"handle"`
	Listed    bool   `json:"listed"` // is the caller publicly listed (opted in)
}

SelfRank is the caller's own standing on a user board, INCLUDED even when the caller falls outside the top-N page. Ranked=false means the caller has no usage in the window (unranked) — the client shows "—", never a fabricated rank.

Jump to

Keyboard shortcuts

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