catalogentry

package
v1.49.43 Latest Latest
Warning

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

Go to latest
Published: Aug 2, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package catalogentry is the CMS source-of-truth for the platform product catalog — the single list docs.<brand>, the console sidebar, and pricing all derive from. commerce owns the DATA (source + seed + edits); the shape is the @hanzo/products contract (that package owns the schema + the iconKey→component and brandColor→css code-maps). Pricing is native: an entry references a pricing plan by key (PricingId), or carries a fixed PriceCents.

Conformance (GET /v1/commerce/catalog → the @hanzo/products CatalogEntry):

  • iconKey is a @hanzogui/lucide-icons-2 export NAME ("Brain") — never a component.
  • brandColor is a swatch KEY ("violet") — never hex. @hanzo/products maps key→css.
  • category is EXACTLY one of the 10 canonical categories (others are dropped by scope).
  • route is "/<slug>", apiPath is /v1-prefixed, docsUrl is /docs/services/<slug>.
  • pricingId is a pricing plans/<key>.json key, or null.
  • brands is a category-derived convenience — NOT a hand-authored filter; the server scopes by CATEGORY (categoriesForBrand), matching @hanzo/products catalogForBrand.

Index

Constants

View Source
const (
	StatusEnabled  = "enabled"  // in-console module that works
	StatusExternal = "external" // live external brand surface
	StatusSoon     = "soon"     // primitive shipped, no console surface yet
)

Status enumerates a product's honest enablement state (mirrors the console registry): a working in-console module, a live external surface, or a primitive that ships with no console surface yet. No fabricated states.

View Source
const (
	FamilyEnso       = "enso"
	FamilyZen        = "zen"
	FamilyThirdParty = "third-party"
)

Model families. Enso and Zen are OURS and are presented as ours — never described in terms of an upstream model. Everything we resell is third-party, named plainly by its VENDOR ("Meta", never "Meta Llama").

View Source
const (
	CategoryEnso  = "enso"
	CategoryZen   = "zen"
	CategoryModel = "model" // third-party, grouped for display by ModelSpec.Vendor
)

Model categories. Following the infra-tier precedent exactly: model rows get their own categories, surfaced under the brand-neutral "models" SCOPE, so they never pollute the ten canonical cloud-primitive product categories. Our two families are their own categories — that IS the split between ours and resold, expressed as data rather than as a naming convention.

View Source
const (
	RateIn         = "in"
	RateOut        = "out"
	RateCacheRead  = "cacheRead"
	RateCacheWrite = "cacheWrite"
)

Rate keys. A single-component price (a VM, a plan) leaves Key empty.

View Source
const (
	UnitMTok   = "mtok" // per million tokens
	UnitMonth  = "month"
	UnitHour   = "hour"
	UnitImage  = "image"
	UnitCall   = "call"
	UnitSecond = "second"
)

Rate units.

View Source
const DefaultMarkup = "1.20"

DefaultMarkup is the retail multiple applied to a synced upstream cost when an entry sets no Markup of its own. It matches the 1.20 spread the OpenRouter resale already ran at, so making markup per-entry changes no customer's price on the day it lands.

View Source
const OpenRouterURL = "https://openrouter.ai/api/v1/models"

OpenRouterURL is the upstream catalog endpoint.

View Source
const ServesOpenRouter = "openrouter"

ServesOpenRouter is the family that answers an OpenRouter-sourced slug, and the SCOPE of an OpenRouter sync run — see Refresh, which only ever withdraws models this upstream is responsible for.

View Source
const WithdrawnUpstream = "withdrawn by the upstream provider"

WithdrawnUpstream is the reason stamped on a model the upstream stopped publishing. It is the entry's ModelSpec.Unavailable — visible to every caller, because a model you cannot call for a stated reason is an honest listing and a model that silently disappeared is not.

Variables

View Source
var ErrEmptyUpstream = errors.New("refusing to sync an empty upstream catalog")

ErrEmptyUpstream is a REFUSAL, not a result. See the fail-safe above.

Functions

func CategoryForFamily added in v1.49.25

func CategoryForFamily(family string) string

CategoryForFamily maps a family to the category its entries live in, so a syncer never has to know the taxonomy.

func CategorySlug

func CategorySlug(label string) string

CategorySlug slugifies a category label the way @hanzo/products categorySlug does: simple lowercase ("AI"→"ai", "Web3"→"web3").

func IsModel added in v1.49.25

func IsModel(e *CatalogEntry) bool

IsModel reports whether an entry is a model row.

func Namespace added in v1.49.26

func Namespace(slug string) string

Namespace is the vendor segment of a model slug, with OpenRouter's floating "~" alias marker removed so "~openai" and "openai" are ONE vendor.

func Query

func RateMarginPct added in v1.49.25

func RateMarginPct(r Rate, markup string) *float64

RateMarginPct returns the gross margin of a rate's retail price over its upstream cost, as a percentage rounded to two decimals: (price-cost)/price. nil when either side is unknown, so an uncomputable margin is an absent field rather than a misleading 0 or 100.

func Seed

func Seed(db *datastore.Datastore) (created int, err error)

Seed loads the embedded @hanzo/products snapshot into db (which MUST be namespaced to the catalog-owning "system" org). IDEMPOTENT + NON-DESTRUCTIVE: an entry whose slug already exists is left UNTOUCHED so CMS edits are never clobbered; only missing slugs are created. Order is the snapshot index (stable display order within a category). Returns the number created.

func SeedEnsoModels added in v1.49.37

func SeedEnsoModels(db *datastore.Datastore) (created int, err error)

SeedEnsoModels loads the embedded Enso family into db (which MUST be namespaced to the catalog-owning "system" org). IDEMPOTENT + NON-DESTRUCTIVE exactly like Seed and SeedInfra: a slug that already exists is left UNTOUCHED, so a price an admin has since edited is never reset to the seed. Only missing slugs are created. Returns the number created.

func SeedEnsoModelsIfEmpty added in v1.49.37

func SeedEnsoModelsIfEmpty(db *datastore.Datastore) (created int, err error)

SeedEnsoModelsIfEmpty seeds the Enso family only when NO enso row is present yet, so it is safe on every bootstrap (mirrors SeedInfraIfEmpty). Gated on the enso category alone — per-family, so seeding Enso is independent of Zen, of the third-party mirror, and of the product catalog. Once any enso row exists it never re-runs, leaving CMS state (including deletions) authoritative.

func SeedIfEmpty

func SeedIfEmpty(db *datastore.Datastore) (created int, err error)

SeedIfEmpty seeds the catalog only when it is currently empty — a cheap single count query gates the full per-row create, so it is safe to call on every bootstrap. Once any entry exists (seeded or CMS-created) it never re-runs, so CMS state stays authoritative.

func SeedInfra added in v1.49.9

func SeedInfra(db *datastore.Datastore) (created int, err error)

SeedInfra loads the embedded infra-tier snapshot into db (which MUST be namespaced to the catalog-owning "system" org). IDEMPOTENT + NON-DESTRUCTIVE, exactly like Seed: an entry whose slug already exists is left UNTOUCHED so CMS edits are never clobbered; only missing slugs are created. Returns the number created.

func SeedInfraIfEmpty added in v1.49.9

func SeedInfraIfEmpty(db *datastore.Datastore) (created int, err error)

SeedInfraIfEmpty seeds the infra tiers only when NONE are present yet — a cheap per-category count gates the full per-row create, so it is safe to call on every bootstrap (mirrors SeedIfEmpty). Once any infra tier exists (seeded or CMS-edited) it never re-runs, so CMS state — including deletions — stays authoritative. The gate is SEPARATE from the whole-catalog SeedIfEmpty gate, so the infra tiers seed independently of the 95-row hanzo product snapshot.

func SplitVendor added in v1.49.26

func SplitVendor(display string) (vendor, name string)

SplitVendor decomplects an upstream display name into the PARTY and the PRODUCT: "Meta: Llama 4 Maverick" becomes "Meta" and "Llama 4 Maverick".

Carrying "Meta Llama" as one string is the vendor+product conflation we do not want: it makes grouping by vendor impossible without re-parsing a display string, and it reads as though the vendor were named after its product. The vendor here is only THIS model's opinion; resolveVendors settles the namespace's actual name across the whole payload.

func SystemDB added in v1.49.26

func SystemDB(ctx context.Context) *datastore.Datastore

SystemDB returns a datastore scoped to the platform catalog namespace.

Namespacing here is by CONTEXT, which is the only namespacing the SQL layer honors — not datastore.NewNamespaced, which fail-closes to a nil DB for reserved namespaces. A nil DB reads as "the catalog is empty", and code that prices things off an empty catalog reports zero cost, so this distinction is worth the comment.

A nil ctx is treated as Background rather than panicking: callers reach the catalog from request handlers, from cron, and from process start, and only the first has a ctx to pass.

func TokenCostCents added in v1.49.26

func TokenCostCents(e *CatalogEntry) (in, out float64, ok bool)

TokenCostCents reports what one million input tokens and one million output tokens of this entry cost US upstream, in cents. ok is false when the entry prices no per-token component at all.

This is a REPORTING unit, not a billing one. Cents-per-Mtok is not integral — an upstream at $0.000000003625 per token is 0.3625 c/Mtok — so it cannot be a currency.Cents, and float64 is only admissible because the caller rounds to whole cents over an aggregate of many rows. To BILL a rate, parse Rate.Cost as money and multiply there; never round-trip through this.

The rules below are each a refusal to guess:

  • Unit must be UnitMTok. RatesOf synthesizes month/hour rates for infra rows, and reading one of those as a per-token figure produces a number that is wrong by orders of magnitude rather than merely imprecise.

  • Only the MaxContext == 0 rate is read. Context rungs are data with no selector in this repo, and a token ledger carries no context length to pick one with. An entry that prices ONLY rungs reports ok=false and lets the caller fall back, which is honest; guessing a rung is not.

  • An absent in/out component is zero, not unknown. The upstream decoder omits a component the provider prices at zero, so absence there means free.

  • An entry with no parseable mtok cost at all reports ok=false. A sync can wipe rates when an upstream stops publishing them, and an absence must never be published as a zero cost — that reads downstream as 100% margin.

Types

type AdminCatalog added in v1.49.7

type AdminCatalog struct {
	Brand      string      `json:"brand"`
	Categories []Category  `json:"categories"`
	Products   []AdminItem `json:"products"`
}

AdminCatalog is the projection returned by GET /v1/commerce/admin/catalog. It mirrors Catalog but its products carry cost + marginPct so admin.hanzo.ai can administrate margin.

func ProjectAdmin added in v1.49.7

func ProjectAdmin(db *datastore.Datastore, brand string) (AdminCatalog, error)

ProjectAdmin returns the admin brand-scoped catalog: the public projection PLUS cost + marginPct on every entry. Callers MUST gate this on owner=="admin"; the projection itself only adds the administrative economics. MarginPct falls back to the derived (price-cost)/price margin when the entry stores no explicit override.

type AdminItem added in v1.49.7

type AdminItem struct {
	Item
	CostCents currency.Cents `json:"costCents"`
	MarginPct float64        `json:"marginPct"`

	// Markup is the entry's retail multiple over cost, and AdminRates is the
	// per-component cost/price/margin the CTO reads "on each / all".
	Markup     string          `json:"markup,omitempty"`
	AdminRates []AdminRateView `json:"adminRates,omitempty"`
}

AdminItem is the admin projection of a CatalogEntry: the full public Item PLUS the administrative economics (cost + margin) the public projection withholds. Only the owner=="admin" admin catalog emits it, so upstream cost and target margin never reach a public reader.

type AdminRateView added in v1.49.25

type AdminRateView struct {
	RateView
	Cost      string   `json:"cost,omitempty"`
	MarginPct *float64 `json:"marginPct,omitempty"`
}

AdminRateView is the admin projection of one Rate: the retail price PLUS the upstream cost we pay and the resulting margin. MarginPct is a pointer so an uncomputable margin is an absent field, never a fabricated 0 — and a NEGATIVE margin is shown plainly rather than suppressed, because selling under cost is a decision that must be visible.

type Catalog

type Catalog struct {
	Brand      string     `json:"brand"`
	Categories []Category `json:"categories"`
	Products   []Item     `json:"products"`
}

Catalog is the full PUBLIC projection returned by GET /v1/commerce/catalog. `products` is the CatalogEntry[] @hanzo/products consumes; brand + categories are additive envelope fields. It carries metadata + the public price (Item.PriceCents) + the plan reference (Item.PricingId) + the public pricing block (Item.Pricing) — NEVER cost or margin.

func Project

func Project(db *datastore.Datastore, brand string) (Catalog, error)

Project returns the PUBLIC brand-scoped catalog: metadata + public price + plan reference + public pricing block, NEVER cost or margin.

type CatalogEntry

type CatalogEntry struct {
	mixin.Model[CatalogEntry]

	Slug        string `json:"slug"`                             // stable id / path segment, e.g. "gateway"
	Name        string `json:"name"`                             // "Gateway"
	Category    string `json:"category"`                         // one of the 10 canonical categories
	Description string `json:"description" datastore:",noindex"` // one-line (additive; not in the core contract)
	Gcp         string `json:"gcp,omitempty"`                    // GCP product it stands in for

	// Presentation KEYS (CMS-editable; @hanzo/products resolves them).
	IconKey    string `json:"iconKey"`    // lucide export name, e.g. "Network"
	BrandColor string `json:"brandColor"` // swatch key, e.g. "blue"

	Route     string `json:"route"`               // marketing "/<slug>"
	DocsUrl   string `json:"docsUrl"`             // https://docs.hanzo.ai/docs/services/<slug>
	ApiPath   string `json:"apiPath"`             // /v1-prefixed path, "/v1/<slug>"
	ApiRoute  string `json:"apiRoute,omitempty"`  // host-qualified "api.hanzo.ai/v1/<slug>"
	GithubUrl string `json:"githubUrl,omitempty"` // source runtime repo URL
	External  bool   `json:"external,omitempty"`  // leaf that links out to another brand surface

	// Pricing is the public pricing block; Private is the admin-only unit
	// economics (cost + margin) — stored as noindex blobs, projected apart:
	// Pricing rides the public projection, Private only the super-admin list.
	Pricing  *Pricing   `json:"pricing,omitempty" datastore:"-"`
	Pricing_ string     `json:"-" datastore:",noindex"`
	Private  *Economics `json:"private,omitempty" datastore:"-"`
	Private_ string     `json:"-" datastore:",noindex"`

	// Rates is the entry's price VECTOR — the one way a price is expressed. A
	// model charges per input/output/cache component and per context rung; a VM
	// charges one amount per month. Both are Rates; the VM is the one-element
	// case. Each rate carries the upstream COST (synced) and the retail PRICE
	// (set in admin), so margin is derived and displayed per component.
	//
	// PriceCents/CostCents below are the LEGACY SCALAR form, still written by
	// the infra-tier seed and read by the pricing service. Every projection goes
	// through RatesOf, which synthesizes the one-element vector from them, so a
	// reader has exactly one shape regardless of which form a row was written
	// in. New writers use Rates.
	Rates  []Rate `json:"rates,omitempty" datastore:"-"`
	Rates_ string `json:"-" datastore:",noindex"`

	// Markup is the retail multiple over a synced upstream cost, as an exact
	// decimal string ("1.20"), applied to any rate that carries no explicit
	// Price. Empty ⇒ DefaultMarkup. It is PER ENTRY and editable — never a
	// global constant buried in a service's env, which is a margin nobody can
	// read.
	Markup string `json:"markup,omitempty"`

	// Spec is the model DESCRIPTOR + routing policy, present only on model rows
	// (see model.go). Stored as a noindex blob like Pricing/Private.
	Spec  *ModelSpec `json:"spec,omitempty" datastore:"-"`
	Spec_ string     `json:"-" datastore:",noindex"`

	// PricingId references a pricing plan by key (plans/<key>.json); empty ⇒
	// projected as JSON null. PriceCents is an optional native fixed-price
	// override (additive to the contract) — the PUBLIC price a customer pays.
	PricingId  string         `json:"pricingId"`
	PriceCents currency.Cents `json:"priceCents,omitempty"`
	Currency   currency.Type  `json:"currency,omitempty"`

	// CostCents is the platform's own unit cost for this product (what Hanzo
	// pays upstream) and MarginPct is the target gross margin over it. Both are
	// administrative economics — the margin surface admin.hanzo.ai edits (HIP-0106)
	// — and are projected ONLY by the owner=="admin" admin catalog, NEVER by the
	// public projection. When MarginPct is unset the admin projection derives it
	// from (PriceCents-CostCents)/PriceCents so a stored override and a computed
	// value read the same way.
	CostCents currency.Cents `json:"costCents,omitempty"`
	MarginPct float64        `json:"marginPct,omitempty"`

	Status string `json:"status" orm:"default:enabled"` // enabled|external|soon
	Repo   string `json:"repo,omitempty"`               // source repo, e.g. "hanzoai/ai"
	Admin  bool   `json:"admin,omitempty"`              // admin-gated surface

	// Brands is a category-derived convenience preserved from the seed (the
	// server filters by category, not by this list). Stored as a noindex blob.
	Brands  []string `json:"brands,omitempty" datastore:"-"`
	Brands_ string   `json:"-" datastore:",noindex"`

	Order     int  `json:"order"`                        // display rank within a category
	Published bool `json:"published" orm:"default:true"` // gates from the public projection

	// ProductId optionally links this catalog surface to a real commerce
	// product for checkout/subscription. Empty = presentation/pricing only.
	ProductId string `json:"productId,omitempty"`

	Metadata  Map    `json:"metadata,omitempty" datastore:"-"`
	Metadata_ string `json:"-" datastore:",noindex"`
}

CatalogEntry is one product in the platform catalog. Slug (== id) is the stable, globally-unique key the entry is addressed by.

func New

func (*CatalogEntry) Load

func (e *CatalogEntry) Load(ps []datastore.Property) (err error)

func (*CatalogEntry) Save

func (e *CatalogEntry) Save() ([]datastore.Property, error)

type Category

type Category struct {
	ID    string `json:"id"`    // slugified label, e.g. "ai"
	Label string `json:"label"` // "AI"
	Order int    `json:"order"` // display rank
}

Category is one taxonomy entry in the projection.

type Economics added in v1.49.7

type Economics struct {
	Cost      string   `json:"cost"`
	MarginPct *float64 `json:"marginPct,omitempty"`
}

Economics is the PRIVATE, admin-only unit economics for a capability. It is NEVER included in the public projection — only the super-admin ListEntries surface returns it. Cost is a display string ("$0.00893 / hour …") or "TODO"; MarginPct is the gross margin percent, nil when not yet computed (so an unknown margin is an absent field, never a fabricated 0).

type InfraSeedRow added in v1.49.9

type InfraSeedRow struct {
	Slug        string                 `json:"slug"`
	Name        string                 `json:"name"`
	Category    string                 `json:"category"`
	Description string                 `json:"description"`
	PriceCents  currency.Cents         `json:"priceCents"`
	Currency    currency.Type          `json:"currency"`
	Order       int                    `json:"order"`
	Metadata    map[string]interface{} `json:"metadata"`
}

InfraSeedRow is one infra-tier seed record: a catalog-entry addressed by a globally-unique slug, carrying its display price (PriceCents) and the full structured spec in the Metadata JSON hatch (vcpus/memoryGB/… for cloud, gpu/vram/price for gpu, replicas/ramGiB/…/usage for datastore). It is a deliberately SMALLER shape than SeedRow — infra tiers are pricing rows, not console modules, so they carry no iconKey/brandColor/route/apiPath.

func InfraSeedRows added in v1.49.9

func InfraSeedRows() ([]InfraSeedRow, error)

InfraSeedRows returns the parsed embedded infra-tier snapshot.

type Item

type Item struct {
	ID         string   `json:"id"` // == Slug (stable, unique)
	Name       string   `json:"name"`
	Category   string   `json:"category"`
	BrandColor string   `json:"brandColor"`
	IconKey    string   `json:"iconKey"`
	Slug       string   `json:"slug"`
	Route      string   `json:"route"`
	DocsUrl    string   `json:"docsUrl"`
	ApiPath    string   `json:"apiPath"`
	ApiRoute   string   `json:"apiRoute,omitempty"`  // host-qualified api.hanzo.ai/v1/<slug>
	GithubUrl  string   `json:"githubUrl,omitempty"` // source runtime repo URL
	PricingId  *string  `json:"pricingId"`           // string OR null
	Brands     []string `json:"brands,omitempty"`    // category-derived convenience

	// Additive (client ignores unknowns).
	Description string         `json:"description,omitempty"`
	Gcp         string         `json:"gcp,omitempty"`
	Status      string         `json:"status,omitempty"`
	Repo        string         `json:"repo,omitempty"`
	External    bool           `json:"external,omitempty"`
	Admin       bool           `json:"admin,omitempty"`
	PriceCents  currency.Cents `json:"priceCents,omitempty"`
	Currency    currency.Type  `json:"currency,omitempty"`
	Order       int            `json:"order,omitempty"`
	ProductId   string         `json:"productId,omitempty"`

	// Pricing is the PUBLIC pricing block. Private economics (cost/margin) are
	// deliberately absent here — they ride only the owner=="admin" admin
	// projection (AdminItem), never this public projection.
	Pricing *Pricing `json:"pricing,omitempty"`

	// Rates is the public price VECTOR — retail only, one element per metered
	// component and context rung. Every entry projects it, synthesized from the
	// legacy scalar when a row predates Rates, so a reader has one shape.
	Rates []RateView `json:"rates,omitempty"`

	// Spec is the model descriptor + routing policy, present on model rows. It
	// is projected to EVERY caller: minTier and enabled decide what a caller may
	// USE, never what they may SEE.
	Spec *ModelSpec `json:"spec,omitempty"`

	// Metadata is the structured-spec JSON hatch (the entry's Metadata map),
	// projected verbatim. Empty for console products (omitted); for the infra
	// tiers it carries the machine-readable spec (vcpus/memoryGB/… for cloud,
	// gpu/vram/price for gpu, replicas/ramGiB/…/usage for datastore) that the
	// pricing service maps back into its cloudPlans/gpuTiers/datastore shapes.
	// It is public presentation/pricing data only — never cost or margin.
	Metadata map[string]interface{} `json:"metadata,omitempty"`
}

Item is the public projection of a CatalogEntry — the exact @hanzo/products CatalogEntry shape. Presentation keys (iconKey, brandColor) are strings resolved client-side; pricingId is null when unset. Fields beyond the core contract (gcp, repo, admin, status, description, priceCents, order, productId) are additive — the client ignores unknowns.

type ModelRow added in v1.49.25

type ModelRow struct {
	Slug        string    `json:"slug"`
	Name        string    `json:"name"`
	Description string    `json:"description,omitempty"`
	Spec        ModelSpec `json:"spec"`
	Costs       []Rate    `json:"costs,omitempty"`
	Order       int       `json:"order,omitempty"`
	Metadata    Map       `json:"metadata,omitempty"`
}

ModelRow is what a SYNCER publishes for one model — the seam between the families and commerce. The family repos (enso, zen) and the upstream (OpenRouter) own the STRUCTURE: which SKUs exist, what each means, and the per-rung shape of their cost. Commerce holds the NUMBERS and admin edits them.

Costs is a cost-only rate vector: a syncer states what we PAY, per component and per context rung, and nothing else. UpsertModels enforces that.

func DecodeOpenRouter added in v1.49.26

func DecodeOpenRouter(body []byte) ([]ModelRow, error)

DecodeOpenRouter parses an OpenRouter /models payload into ModelRows.

It fails on a malformed or empty body rather than returning what it managed to read. A half-decoded catalog looks exactly like an upstream that dropped two hundred models, and the sync's answer to models disappearing is to withdraw them — so "upstream shrank" and "we failed to read it" have to be told apart HERE, because after this function they are indistinguishable.

func FetchOpenRouter added in v1.49.26

func FetchOpenRouter(ctx context.Context, apiKey string) ([]ModelRow, error)

FetchOpenRouter reads the live upstream catalog. It is the ONLY I/O in the sync path, deliberately split from DecodeOpenRouter so every rule about what a payload MEANS is testable against a fixture with no network.

The API key is optional (the catalog is public) but is sent when present so the request is attributed to our account.

type ModelSeedRow added in v1.49.37

type ModelSeedRow struct {
	Slug        string    `json:"slug"`
	Name        string    `json:"name"`
	Description string    `json:"description"`
	Published   bool      `json:"published"`
	Order       int       `json:"order"`
	Spec        ModelSpec `json:"spec"`
	Rates       []Rate    `json:"rates"`
}

ModelSeedRow is one first-party model's opening state: the descriptor a family owns plus the rate vector carrying BOTH the upstream cost and the retail price. It is deliberately not ModelRow — a ModelRow is what a SYNCER publishes and may state only cost, whereas a seed states the initial admin decision and therefore may state price.

func EnsoSeedRows added in v1.49.37

func EnsoSeedRows() ([]ModelSeedRow, error)

EnsoSeedRows returns the parsed embedded Enso family snapshot.

type ModelSpec added in v1.49.25

type ModelSpec struct {
	Vendor string `json:"vendor,omitempty"` // plain vendor name: "Meta", never "Meta Llama"
	Family string `json:"family"`           // enso | zen | third-party

	// Routing: which family service answers this slug, and under what id
	// upstream. Routing is DATA on the one catalog — never a hardcoded pin in
	// a router, which is how a model gets listed that nothing can serve.
	Serves   string `json:"serves,omitempty"`   // enso | zen | openrouter | …
	Upstream string `json:"upstream,omitempty"` // model id at Serves, when it differs from Slug

	Modality      string   `json:"modality,omitempty"` // chat | embedding | image | audio | video | rerank
	ContextWindow int      `json:"contextWindow,omitempty"`
	MaxOutput     int      `json:"maxOutput,omitempty"`
	Capabilities  []string `json:"capabilities,omitempty"` // vision, tools, …

	// MinTier is the entitlement floor ("" | trial | paid). It gates USE, and
	// is projected to every caller so the reason is honest and legible.
	MinTier string `json:"minTier,omitempty"`

	// Enabled says the model is routable right now. A listed-but-disabled model
	// (upstream withdrawn, capacity pulled) stays VISIBLE with its price and
	// Unavailable set — deleting the row would break the billing history that
	// references it.
	Enabled     bool   `json:"enabled"`
	Unavailable string `json:"unavailable,omitempty"` // honest reason when !Enabled
}

ModelSpec is the model DESCRIPTOR: what the model is, and the routing policy that says exactly how it may be used. It rides one entry as a noindex blob, alongside the generic price vector.

VISIBILITY AND ENTITLEMENT ARE DIFFERENT THINGS and this type keeps them apart. CatalogEntry.Published is the ONLY hide switch and exists for a genuinely private entry (a BYO-key model scoped to one org). Everything else — MinTier, Enabled, Unavailable — decides what a caller may USE and what they are CHARGED, never what they may SEE. A model a caller cannot yet afford is an upsell: it appears with its price and an honest reason, never omitted. A customer seeing fewer models than an anonymous visitor is the bug, not the feature.

type Pricing added in v1.49.7

type Pricing struct {
	PublicPrice string   `json:"publicPrice"`
	PlanTiers   []string `json:"planTiers,omitempty"`
	UsageMeter  string   `json:"usageMeter,omitempty"`
}

Pricing is the PUBLIC pricing block for a capability — projected to everyone. PublicPrice is a human display string ("From $0.10 / 1M input tokens"), or the literal "TODO" when a real number is not yet sourced (never a fabricated one). PlanTiers are subscription plan keys (api/billing/plans/<key>.json) the capability is included in; UsageMeter is the metering unit ("per_mtok").

type Rate added in v1.49.25

type Rate struct {
	Key        string `json:"key,omitempty"`        // "" (single) | in | out | cacheRead | cacheWrite
	Unit       string `json:"unit"`                 // mtok | month | hour | image | call | second
	MaxContext int    `json:"maxContext,omitempty"` // rung ceiling this rate applies up to; 0 = all
	Cost       string `json:"cost,omitempty"`       // upstream unit cost, USD — SYNCED
	Price      string `json:"price,omitempty"`      // retail unit price, USD — SET IN ADMIN
}

Rate is ONE metered component of an entry's economics, in ONE unit.

A price is a VECTOR, not a scalar: a model charges separately for input, output and cached reads, and may charge a different amount above a context rung. A VM is the degenerate one-element case (one rate, unit "month"). One shape expresses both, so there is exactly one way to read a price.

Cost is what WE pay upstream — written only by a sync, never by hand. Price is what the CUSTOMER pays — set in admin.hanzo.ai, never by a sync. Margin is DERIVED from the two and displayed; it is never stored as a separate truth. When Price is empty the retail price is Cost x the entry's Markup, so a long tail of synced models needs no per-row hand-pricing.

Amounts are exact decimal strings in USD per Unit. They are NOT cents: currency.Cents is an int64 and cannot represent $0.000015 per token, and a float would lose the upstream's published precision.

func RatesOf added in v1.49.25

func RatesOf(e *CatalogEntry) []Rate

RatesOf returns an entry's price vector: the stored Rates when it has them, else the degenerate vector synthesized from its scalar PriceCents/CostCents. This is the ONE read path for a price — every projection goes through it, so a model and a VM are read the same way.

func (Rate) RetailPrice added in v1.49.25

func (r Rate) RetailPrice(markup string) string

RetailPrice returns what the customer pays for this rate: the explicit Price when an admin set one, else Cost x markup. Empty when neither is known — an unknown price is an absent field, never a fabricated 0.

type RateView added in v1.49.25

type RateView struct {
	Key        string `json:"key,omitempty"`
	Unit       string `json:"unit"`
	MaxContext int    `json:"maxContext,omitempty"`
	Price      string `json:"price,omitempty"`
}

RateView is the PUBLIC projection of one Rate: what the customer pays, per unit and per context rung. Cost and margin are deliberately absent — they ride only AdminRateView.

type RefreshResult added in v1.49.26

type RefreshResult struct {
	Serves    string   `json:"serves"`
	Upstream  int      `json:"upstream"`
	Created   int      `json:"created"`
	Updated   int      `json:"updated"`
	Withdrawn []string `json:"withdrawn,omitempty"`
	Restored  []string `json:"restored,omitempty"`
	SyncedAt  string   `json:"syncedAt"`
}

RefreshResult is what one run did, in the terms a review needs: what the upstream published, what moved, and what stopped being callable.

func Refresh added in v1.49.26

func Refresh(db *datastore.Datastore, serves string, rows []ModelRow) (RefreshResult, error)

Refresh lands rows for the upstream named by serves and withdraws whatever that upstream no longer publishes.

The sweep is SCOPED BY SERVES, so one upstream going dark can never withdraw another family's models: an OpenRouter outage cannot touch Enso or Zen.

It is idempotent in the sense that matters — the same upstream payload yields the same catalog state, and a second run withdraws and restores nothing.

db MUST be namespaced to the catalog-owning "system" org.

type SeedRow

type SeedRow struct {
	ID         string     `json:"id"`
	Name       string     `json:"name"`
	Category   string     `json:"category"`
	BrandColor string     `json:"brandColor"`
	IconKey    string     `json:"iconKey"`
	Slug       string     `json:"slug"`
	Route      string     `json:"route"`
	DocsUrl    string     `json:"docsUrl"`
	ApiPath    string     `json:"apiPath"`
	ApiRoute   string     `json:"apiRoute"`
	GithubUrl  string     `json:"githubUrl"`
	External   bool       `json:"external"`
	PricingId  string     `json:"pricingId"` // null → ""
	Pricing    *Pricing   `json:"pricing"`
	Private    *Economics `json:"private"`
	Brands     []string   `json:"brands"`
	Repo       string     `json:"repo"`
	Admin      bool       `json:"admin"`
	Status     string     `json:"status"`
	Gcp        string     `json:"gcp"`
}

SeedRow is the @hanzo/products snapshot shape (the exact CatalogEntry contract). `id` == `slug`; `pricingId` may be JSON null (→ "").

func HanzoSeedRows

func HanzoSeedRows() ([]SeedRow, error)

HanzoSeedRows returns the parsed embedded snapshot.

type SyncStats added in v1.49.25

type SyncStats struct {
	Created int `json:"created"`
	Updated int `json:"updated"`
}

SyncStats reports what a sync did, so a run is legible without diffing rows.

func UpsertModels added in v1.49.25

func UpsertModels(db *datastore.Datastore, rows []ModelRow) (SyncStats, error)

UpsertModels lands a syncer's view of the model catalog.

THE ONE RULE, and the reason this function exists rather than a generic upsert: A SYNC OWNS COST, ADMIN OWNS PRICE. On an existing entry it refreshes the upstream cost and the machine-observable facts (what the model is, where it routes) and touches NOTHING else — not Price, not Markup, not MinTier, not Published, not Name. An upstream price move therefore surfaces as a margin moving in admin.hanzo.ai, and can never silently change what a customer pays.

The rate SET follows upstream (a withdrawn context rung disappears, a new one appears) but each surviving rate keeps the Price a human put on it, matched by rate identity (key, unit, rung).

A row for a slug that does not exist yet is CREATED whole — including its policy defaults — because at birth there is no human decision to preserve.

Jump to

Keyboard shortcuts

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