Documentation
¶
Overview ¶
Admin surface for the catalog enablement overlay (SuperAdmin only).
GET /v1/admin/catalog full catalog + every entry's state
PATCH /v1/admin/catalog/models/* upsert one model overlay (id may
contain '/', e.g. anthropic/x)
PATCH /v1/admin/catalog/providers/:name upsert one provider overlay
Gating mirrors the rest of cloud: c.IsAdmin() is the gateway-minted X-User-IsAdmin claim, set only on the JWT-validated path (HIP-0026) for members of the global `admin` org. Same trust model the pricing /sync trigger and provisioning already rely on. Non-admins get 403, never the catalog state.
Catalog enablement overlay: the ONE mutable state Hanzo Cloud lays over the static @hanzo/pricing catalog, plus the ONE gate that applies it on read.
Decomplected: the goja bundle (data/pricing.json) stays the sole source of truth for catalog CONTENT and SHAPE. This file adds only per-entry STATE — {enabled, betaOrgs, overrides} keyed by (kind,id) — and a pure function that filters + merges that state onto the bundle's output. Go never reshapes a model; it only hides entries and merges an admin override patch on top.
Default is "everything enabled": a model/provider with no overlay row is visible to every org, unchanged. An empty store therefore leaves the catalog exactly as the bundle ships it — no fabricated state, no regression for live customers until an admin acts.
The enablement REGISTRY surface (#30/#31) — the three-level model over the ONE catalog overlay store: global off|beta|ga, per-org beta grants, and user self-service beta opt-in. It reuses the SAME overlay the /v1/pricing catalog gate and /v1/admin/catalog admin surface use — there is ONE enablement registry and ONE resolver (Overlay.visibleTo / .State), never a parallel copy.
GET /v1/admin/enablement SuperAdmin: the full managed registry PUT /v1/admin/enablement SuperAdmin: set an item off|beta|ga (+ grant orgs) GET /v1/enablement any authed: the caller's EFFECTIVE view + betas POST /v1/enablement/optin authed: opt the caller's OWN org into a beta POST /v1/enablement/optout authed: opt the caller's own org back out
SECURITY — the two-way crux RED verifies:
- Global state is SUPERADMIN only (c.IsAdmin()). A customer/org-admin can never flip an item's off/beta/ga.
- Self opt-in is scoped to the VALIDATED caller org (principal.Org — a gateway-minted principal, NOT a raw client X-Org-Id, which SanitizeIdentity restores on the bearer-less path) — so a user can only ever enable their OWN org, and only for a BETA item: the store's OptIn refuses `ga` (already on) and `off` (the kill switch), so an opt-in can never bypass an admin `off`.
Package pricing mounts the @hanzo/pricing service into the unified cloud binary under /v1/pricing/* (+ the /v1/models, /v1/gpu, /v1/tools aliases), per HIP-0106.
HONEST GOJA STATUS: @hanzo/pricing is an EXPRESS app. Express needs Node's http/net stack and CANNOT run in goja. So the Express *transport* is dropped and replaced by native zip routes; the pricing *handlers* (pure transforms over data/pricing.json + the @hanzo/plans catalog) run in goja via the goja/bundle.js shipped by github.com/hanzoai/pricing. The sync.mjs MARKUP logic (toMTok/roundPrice/processOpenRouterModel/…) also runs in goja through the bundle's applyMarkup(); the only thing that does NOT run in goja is the live network fetch (OpenRouter/HuggingFace — no fetch/AbortController in goja), which this wrapper performs with Go's net/http and then feeds the raw JSON into applyMarkup. See SyncEnabled.
Module boundary: pricing source + markup logic live in hanzoai/pricing. This wrapper is glue. No pricing data or markup math is reimplemented in Go.
IAM gating + X-Org-Id: read endpoints are open to any authenticated caller (the public pricing catalog). The sync trigger is admin-only (c.IsAdmin()).
Index ¶
- func GateRootData(data map[string]any, snap map[string]Overlay, org string, isAdmin bool)
- func Mount(app cloud.Router, deps cloud.Deps) error
- func RunSync(ctx context.Context) (string, error)
- func Shutdown(context.Context) error
- func VisibleProviders(providers map[string]any, snap map[string]Overlay, org string, isAdmin bool) map[string]any
- type Model
- type Overlay
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GateRootData ¶
GateRootData gates the kitchen-sink root payload (GET /v1/pricing returns the whole pricing blob) IN PLACE, so the root shows the exact same gated catalog as the leaf routes — never an un-gated second source. It filters every field that carries a model or provider identity:
- hanzoModels + thirdPartyModels via VisibleCatalog,
- providers via VisibleProviders,
- the id-reference lists freeModels and families[].models, kept only if the referenced model survived the gate (customers); admins keep every ref.
Aggregate summary counts and non-catalog sections (tools/infrastructure/cloud) carry no catalog identity and are left untouched.
func RunSync ¶
RunSync performs the live third-party model sync: fetch upstream listings (network — Go's net/http, since goja has no fetch), run the markup transform in goja via the bundle's applyMarkup(), and swap the shaped third-party section into the served catalog. Returns an ISO timestamp.
This is the HONEST split: network IO in Go, markup math in JS. Only the dynamic third-party section is refreshed here; the Zen catalog + cloud/DO pricing in sync.mjs need the zen-gateway + DO credentials and stay on the standalone sync path for now.
func VisibleProviders ¶
func VisibleProviders(providers map[string]any, snap map[string]Overlay, org string, isAdmin bool) map[string]any
VisibleProviders filters a provider dict (name -> info) by the provider overlay for org, merging provider overrides (RFC 7386). isAdmin callers get every provider with state annotated under each provider's "_overlay".
Types ¶
type Model ¶
Model is one catalog entry exactly as the @hanzo/pricing bundle emits it (see goja/bundle.js 'models'): an opaque JSON object. The gate reads only the identifier (id, falling back to name) and provider, and passes every other field through untouched — keeping the bundle authoritative for shape.
func VisibleCatalog ¶
VisibleCatalog applies the enablement overlay to the bundle's full model list for org. A model is visible iff its OWN overlay AND its provider's overlay both admit org (enabled, or org on the beta list); an entry with no overlay row is visible by default, so a provider with no row never hides its models. Returned models carry any admin override merged on top (RFC 7386). isAdmin callers receive EVERY model — disabled ones included — each annotated under "_overlay" so the admin UI can render and toggle it.
Pure over (full, snap, org, isAdmin): no IO, no globals. This is the unit under test; the wiring layer fetches `full` from goja and `snap` from the store, then calls it.
type Overlay ¶
type Overlay struct {
Kind string `json:"kind"`
ID string `json:"id"`
Enabled bool `json:"enabled"`
Beta bool `json:"beta,omitempty"`
BetaOrgs []string `json:"betaOrgs,omitempty"`
Overrides json.RawMessage `json:"overrides,omitempty"`
UpdatedAt int64 `json:"updatedAt,omitempty"`
}
Overlay is the mutable enablement STATE for one catalog entry. Zero value (no row) == enabled, no beta orgs, no override; the gate treats an absent row as visible, so an empty store is a no-op.
Tri-state (the #30/#31 enablement model), encoded by (Enabled, Beta):
- ga = Enabled → visible to EVERYONE.
- beta = !Enabled && Beta → hidden from the public; visible to opted-in/ granted orgs (BetaOrgs). Users may SELF-OPT-IN to a beta item.
- off = !Enabled && !Beta → hidden from EVERYONE, absolutely. BetaOrgs are IGNORED — a self-opt-in can never bypass an `off` kill switch.