Documentation
¶
Overview ¶
Package link is the unified AI login manager's registry: the org+user-scoped record of WHICH provider accounts (Claude Max, ChatGPT Plus, a Hanzo API key, a raw provider key) a developer has signed into, ON WHICH MACHINES, with each account's latest usage snapshot. It is the cross-machine view console renders as "AI Providers / Accounts" and the source the redundancy route policy reads.
THE ATOM is a Link: one (user, device, provider, account) binding plus its kind, status, last-seen, and the latest usage snapshot. A device is the (machine, host, os) projection shared by a machine's Links — not a separate stored entity, so there is no device/link join and no orphan-device GC. A user's accounts across every machine are just that user's Links.
NO SECRET LIVES HERE. The provider's OAuth token / API key stays device-local (exactly as @hanzo/usage keeps it, reading each provider's own login to meter usage); the registry holds LINK METADATA + usage snapshots only. The collector (@hanzo/usage's reporter) registers a Link and pushes its usage using the SAME IAM bearer it already carries — never a provider secret.
BILLING FOLLOWS THE ACCOUNT, not the registry. This store never touches commerce — it holds no metering client, so it is structurally incapable of creating a charge. A subscription account's inference bills the user's monthly plan (metered here for visibility only); an api-key / hanzo account's inference bills via commerce on the existing gateway path, unchanged. Each Link carries the mode that says which (BillingMode), so a usage event's billing is explicit.
ISOLATION: org is the tenant key and user is the owner key; every read and write leads with `org=? AND user=?` bound predicates, so a caller sees and mutates only their OWN accounts within their OWN org — fail-closed.
Index ¶
- Constants
- func BillingMode(kind string) string
- func Mount(app *zip.App, deps cloud.Deps) error
- func Shutdown(context.Context) error
- type Link
- type RouteCandidate
- type RoutePlan
- type Sample
- type SessionMatch
- type Sessions
- type Store
- func (s *Store) AccountTotals(ctx context.Context, org, subject string, from, to time.Time) ([]Total, bool)
- func (s *Store) Close() error
- func (s *Store) Get(ctx context.Context, org, subject, id string) (Link, error)
- func (s *Store) HanzoTotals(ctx context.Context, org string, from, to time.Time) ([]Total, bool)
- func (s *Store) List(ctx context.Context, org, subject string) ([]Link, error)
- func (s *Store) ListDevice(ctx context.Context, org, subject, machine string) ([]Link, error)
- func (s *Store) ListLinked(ctx context.Context, org, subject string) ([]Link, error)
- func (s *Store) Revoke(ctx context.Context, org, subject, id string, now int64) (Link, bool, error)
- func (s *Store) RevokeDevice(ctx context.Context, org, subject, machine string, now int64) ([]Link, error)
- func (s *Store) Series(ctx context.Context, org, subject, provider, account, window string, ...) ([]Sample, bool)
- func (s *Store) Upsert(ctx context.Context, x Link) (Link, error)
- func (s *Store) WriteSamples(ctx context.Context, org, subject string, samples []Sample, now time.Time) error
- type Total
- type Usage
Constants ¶
const ( // KindSubscription is a provider account signed in with the user's own // subscription login (Claude Max, ChatGPT Plus). Its inference bills the // user's monthly plan; the registry meters it for visibility only. KindSubscription = "subscription" // KindAPIKey is an account credentialed by an API key (a raw provider key, // or a Hanzo hk- key). Its inference bills via commerce on the gateway path. KindAPIKey = "apikey" )
Link kinds — how an account is credentialed, which decides how its usage bills.
const ( StatusLinked = "linked" StatusRevoked = "revoked" )
Link statuses. linked is active; revoked is a logged-out account whose sessions were stopped. A revoked Link is retained (not deleted) so its usage history and the audit trail survive a log-out.
const ( // BillingPlan: the user's own subscription pays; NO commerce charge. BillingPlan = "plan" // BillingCommerce: usage bills via commerce (the gateway meter), as today. BillingCommerce = "commerce" )
Billing modes — the value a Link and a route candidate carry so "how does this account's usage bill" is explicit, never inferred at the charge site.
const ( Window6h = "6h" WindowDay = "day" WindowWeek = "week" WindowMonth = "month" )
The CANONICAL window value: the ONE closed vocabulary that rate limits, quotas, and usage rollups all share, platform-wide. Lowercase, no case variants, no synonyms — a window is one of exactly these four values.
This declaration is the single source of the vocabulary. (hanzoai/commerce today carries an ad-hoc set — weekly/daily/monthly/hourly plus capitalized variants, and no sub-day window at all; a later commerce pass adopts THESE values. Do not copy the old strings back in.)
const ( ConfidenceExact = "exact" ConfidenceEstimated = "estimated" ConfidencePercentOnly = "percentOnly" ConfidenceUnknown = "unknown" )
Confidence values — how real a sample's numbers are. These mirror @hanzo/usage's UsageDataConfidence EXACTLY (they are the meter's own wire values, already carried on Usage.Confidence), so a value crosses the whole system unmapped.
This is the ANTI-FABRICATION FLAG, and the reason it is a column: a Claude sample is `percentOnly`, so its token counters are 0 because they are UNKNOWN — not because the account consumed nothing. Without this value a reader cannot tell "0 tokens" from "no idea", and would render a fabricated zero. With it, the console renders "—".
const ( SourceAccount = "account" SourceHanzo = "hanzo" )
Sources for the global view: which plane a usage row came from. They are NOT interchangeable and are never added together —
- SourceAccount the provider's OWN plan consumption, metered from the user's own login. Its cost is what the PROVIDER says it charged (0 for a flat subscription); its percent is plan quota. NOT a Hanzo charge.
- SourceHanzo hanzo.cloud_usage — Hanzo-routed inference. COST OF RECORD.
const ( ScopeUser = "user" // the caller's OWN linked accounts (org+subject) ScopeOrg = "org" // the whole org's Hanzo-routed usage )
Scopes for a global-view row: the tenancy the row's numbers cover. The two sources answer at different scopes and the row says which, so a reader never silently compares a user's plan usage against an org's whole spend.
const ( Range1h = "1h" Range24h = "24h" Range7d = "7d" Range30d = "30d" )
Ranges — the closed allowlist for a read window. ONE resolver serves both sides of the global view, so the account rows and the Hanzo rows always cover the SAME period; two resolvers could drift and turn the union into a lie.
Variables ¶
This section is empty.
Functions ¶
func BillingMode ¶
BillingMode returns how an account of this kind bills its usage: a subscription bills the user's plan (no commerce charge), everything else bills via commerce. It is the ONE place the subscription-vs-api-key distinction is decided, so a usage event's billing is a pure function of the account, never re-derived.
Types ¶
type Link ¶
type Link struct {
ID string
Org string
User string // the owning subject (validated principal); a user sees only their own
Machine string // stable machine id (the device key)
Host string // hostname (device label)
OS string // platform label (darwin|linux|windows|…)
Provider string // matches @hanzo/usage providerRegistry id (claude|codex|hanzo|openai|…)
Account string // the subscription/account id or label (e.g. an account email)
Plan string // the plan name from the provider identity (e.g. "Claude Max"); display only
Kind string // subscription | apikey
Status string // linked | revoked
LastSeen int64 // unix; bumped on every usage report
Usage string // JSON of the latest Usage projection ("" until first report)
CreatedAt int64
UpdatedAt int64
}
Link is one (user, device, provider, account) binding. Tenant isolation is the (org, user) pair, enforced on every query. It never stores a secret.
type RouteCandidate ¶
type RouteCandidate struct {
Provider string `json:"provider"`
Account string `json:"account,omitempty"`
Plan string `json:"plan,omitempty"`
Kind string `json:"kind"` // subscription | apikey
Billing string `json:"billing"` // plan | commerce (BillingMode(Kind))
Available bool `json:"available"`
HeadroomPct float64 `json:"headroomPct"` // remaining capacity 0..100
Machine string `json:"machine,omitempty"`
Host string `json:"host,omitempty"`
LinkID string `json:"linkId"`
Reason string `json:"reason,omitempty"` // why unavailable, when Available=false
}
RouteCandidate is one account the policy would route to, in preference order, annotated with how it bills and whether it currently has rate-limit headroom.
type RoutePlan ¶
type RoutePlan struct {
Candidates []RouteCandidate `json:"candidates"`
Primary *RouteCandidate `json:"primary,omitempty"`
GeneratedAt string `json:"generatedAt"`
}
RoutePlan is the ordered redundancy plan for a user's accounts: the candidates in preference order plus the primary the caller would try first.
func Plan ¶
Plan builds the redundancy route plan from a user's LINKED accounts. Ordering: subscription accounts first (the flat-rate pool the user already pays for — used first, and failed over between for redundancy: two Claude Max accounts), then api-key/hanzo accounts (the metered API route). Within each group the order is (available first, then most headroom, then most-recently seen). The primary is the first available candidate; if every account is rate-limited it falls back to the first api-key account (the pay-per-call backstop is always usable) — else nil, an honest "nothing routable right now".
Only linked accounts should be passed (Store.ListLinked); a revoked account is never a candidate. The function is pure and total: any Links in, one plan out.
type Sample ¶ added in v1.801.59
type Sample struct {
Provider string // matches Link.Provider / @hanzo/usage providerRegistry id
Account string // the account label (an identifier, never a secret)
Plan string // the plan name from the provider identity; display only
Kind string // subscription | apikey (validKind) — mirrors Link.Kind
Machine string // the machine that OBSERVED this (an attribute, not a key)
Lane string // the meter's lane id (five_hour|seven_day_opus|…) — the identity
Window string // the canonical class (6h|day|week|month)
WindowMinutes int32 // the duration the meter reported (300 for Claude's 5h)
WindowStart time.Time // WHICH instance of the window this measures — the dedup key
ResetsAt time.Time // when the window resets (RateWindow.resetsAt); zero = unknown
// UsedPct is the lane's used percent, 0..100 — RateWindow.usedPercent. For a
// subscription account this is THE quota signal and often the ONLY one.
//
// QUOTA GAP (deliberate, reported, not faked): there is NO absolute
// used/limit token quota column, because nothing can populate one. The meter
// reports a percent, not a limit (Claude: dataConfidence `percentOnly`). The
// hanzoai/plans catalog expresses `ai.requests_per_min` / `ai.tokens_per_min`
// — per-MINUTE rate limits, a different concept from a window quota — and in
// any case it is HANZO's own plan catalog, which cannot know what Anthropic
// grants a Claude Max plan. An always-zero quota_limit would read as "the
// limit is zero"; absent is honest. It lands as an additive column when a
// source for it exists.
UsedPct float64
Confidence string // exact|estimated|percentOnly|unknown — see the const block
// Synthetic marks a lane the meter FABRICATED (RateWindow.isSyntheticPlaceholder
// — set when a provider returned null for a lane and the adapter filled it in).
// Carried so a made-up lane is never rendered as observed truth.
Synthetic bool
// Absolute counters — UsageTotals. Present only when the source really reports
// them (`exact`); zero otherwise, which Confidence disambiguates.
Requests int64
InputTokens int64
OutputTokens int64
TotalTokens int64
CachedInputTokens int64
// Money — ProviderCostSnapshot, in minor units (cents). CostCents is what the
// PROVIDER says this account spent (Claude's extra_usage overage; 0 for a flat
// subscription — the plan is the charge). CostLimitCents is that meter's budget
// (ProviderCostSnapshot.limit) — a MONEY cap, never a token quota. Neither is a
// Hanzo charge: this plane holds no metering client.
CostCents int64
CostLimitCents int64
Currency string // ProviderCostSnapshot.currencyCode; "" = unknown
}
Sample is one metering lane's consumption of one provider account at one observation.
TENANCY IS NOT ON THE VALUE. Org and subject are bound by the server from the validated principal at the boundary, so a client cannot assert whose usage this is — the shape makes cross-tenant writes unrepresentable rather than merely refused.
func (Sample) Sanitize ¶ added in v1.801.59
Sanitize bounds every field so a warehouse row stays small, finite, and well-formed no matter what a client sends: strings trimmed and length-capped, counters non-negative and clamped, percents coerced into [0,100] even for NaN/Inf, instants bounded to a sane window around `now`, and the two enums defaulted rather than trusted. It is TOTAL (never errors), so the write path always has a safe value — validation of the CLOSED vocabularies (window, kind) is the boundary's job and is a 400, because silently rewriting a caller's window class would corrupt their dash.
It does NOT set the observation clock: the server stamps that, so a client can never backdate a sample or pin a stale one as newest.
type SessionMatch ¶
SessionMatch selects the live sessions a revoke stops. Fields are ANDed with the org; an empty field is "any". A link revoke matches {Host,Provider,Account} (the device+account the sessions ran under); a device revoke matches {Host}. Subject is the REVOKING user; the adapter turns it into the session Actor so a stop only ever reaches that user's OWN sessions — Host/Provider/Account (attacker-set at link upsert) can then only narrow WITHIN them, never widen to a co-tenant's.
type Sessions ¶
type Sessions interface {
Stop(ctx context.Context, org string, m SessionMatch) (int, error)
CountActive(ctx context.Context, org string, m SessionMatch) (int, error)
}
Sessions is the seam to the agent-session control plane (clients/agents, in-process). Revoke stops the sessions that ran under a revoked account/device; the device view counts a machine's active sessions. A nil seam (unit test / no agents mounted) makes revoke skip the stop and the count report 0 — the registry truth (the revoked row) is unaffected.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the login-manager database. ONE SQLite file ({DataDir}/link.db) holds every org's Links; tenancy is the (org, subject) pair. It holds NO metering client — it is structurally incapable of charging commerce.
It also owns the account-usage SERIES in the datastore (datastore.go): the Link row is an account's latest state, the series is its history. Both are the same (org, subject) tenancy, so they live behind one store rather than two seams that could drift apart on isolation. dsReady latches the idempotent warehouse DDL — on success only, so a datastore still connecting at boot is retried, not permanently written off.
func (*Store) AccountTotals ¶ added in v1.801.59
func (s *Store) AccountTotals(ctx context.Context, org, subject string, from, to time.Time) ([]Total, bool)
AccountTotals reads the caller's own account usage per (provider, window).
func (*Store) Get ¶
Get returns one Link by id within the caller's (org, subject) scope, or errNotFound. The (org, subject, id) triple is the key, so another tenant's or another user's id resolves to errNotFound — never a cross-scope read.
func (*Store) HanzoTotals ¶ added in v1.801.59
HanzoTotals reads the org's Hanzo-routed usage per provider over the same window. Its rows are always `exact` — cloud_usage counts real calls we billed.
func (*Store) List ¶
List returns every Link the (org, subject) owns, most-recently-updated first (revoked rows included, so the dashboard can show recent log-outs).
func (*Store) ListDevice ¶
ListDevice returns the accounts on one machine within the caller's scope, most-recently-updated first.
func (*Store) ListLinked ¶
ListLinked returns only the ACTIVE (status=linked) accounts the (org, subject) owns — the set the route policy considers. Revoked accounts are excluded so a logged-out account is never a routing candidate.
func (*Store) Revoke ¶
Revoke marks one Link revoked within the caller's scope and returns it (so the handler can stop the sessions that ran under that account). ok=false when no such Link exists in this (org, subject) — a cross-scope id can neither revoke nor probe. An already-revoked Link is returned as-is (idempotent).
func (*Store) RevokeDevice ¶
func (s *Store) RevokeDevice(ctx context.Context, org, subject, machine string, now int64) ([]Link, error)
RevokeDevice marks EVERY still-linked account on one machine revoked within the caller's scope and returns the rows it revoked (so their sessions can be stopped). A device with no linked accounts revokes nothing (empty slice).
func (*Store) Series ¶ added in v1.801.59
func (s *Store) Series(ctx context.Context, org, subject, provider, account, window string, from, to time.Time) ([]Sample, bool)
Series reads one provider account's window instances. It returns (rows, true) when the warehouse answered and (nil, false) when it is unavailable, so the caller can say "unavailable" instead of showing zeros that read as "you used nothing".
func (*Store) Upsert ¶
Upsert registers a Link, keyed by its (org, subject, machine, provider, account) identity. A first report INSERTs (using x.ID / x.CreatedAt); a repeat UPDATEs in place — bumping last_seen, refreshing the device labels/plan/kind, re-activating a revoked account (status→linked), and replacing the usage snapshot ONLY when a fresh one is supplied (an empty usage on a heartbeat keeps the last good snapshot, mirroring @hanzo/usage's keep-stale-over-flapping rule). It returns the stored row (with its authoritative id, which on a repeat is the original, not x.ID). Org+subject are part of the identity, so a caller can only ever write within their OWN (org, subject) scope.
func (*Store) WriteSamples ¶ added in v1.801.59
func (s *Store) WriteSamples(ctx context.Context, org, subject string, samples []Sample, now time.Time) error
WriteSamples appends the caller's sanitized samples to the warehouse, stamping each with the server's observation clock. Org and subject are the SERVER's values (from the validated principal), bound positionally — a sample cannot carry them, so it cannot forge them.
It is FAIL-SOFT by contract: an absent or blocked datastore is not an error the caller sees. The Link row that the same request refreshes is the durable truth; this series is history beside it, and losing a poll of it must never fail a report or block a device.
type Total ¶ added in v1.801.59
type Total struct {
Source string
Scope string
Provider string
Window string
Requests int64
Tokens int64
CostCents int64
UsedPct float64
Confidence string
Windows int64
}
Total is one provider's usage in the global view, from ONE source at ONE scope.
type Usage ¶
type Usage struct {
SessionPct float64 `json:"sessionPct"` // primary window used %, 0..100
WeeklyPct float64 `json:"weeklyPct"` // secondary window used %, 0..100
ResetsAt string `json:"resetsAt,omitempty"` // primary window reset, RFC3339
Tokens int64 `json:"tokens"` // absolute token total when known
InputTokens int64 `json:"inputTokens,omitempty"` //
OutputTokens int64 `json:"outputTokens,omitempty"` //
SpendCents int64 `json:"spendCents"` // provider spend (0 for a subscription)
Currency string `json:"currency,omitempty"` //
Confidence string `json:"confidence,omitempty"` // exact|estimated|percentOnly|unknown
UpdatedAt string `json:"updatedAt,omitempty"` // snapshot time, RFC3339
}
Usage is the projection of a provider's @hanzo/usage UsageSnapshot the collector pushes and the dashboard renders: the rate-limit windows, token totals, and spend. It is stored as JSON on the Link and parsed for the route policy's headroom. Money is USD cents end-to-end. Spend is always 0 for a pure subscription account (there is no per-call charge — the plan is flat).