Files
commerce/models/catalogentry/seed_test.go
zeekayandHanzo Dev ae0b10a4b9 The catalog names the addresses the fleet publishes
Sixteen products advertised a path nothing on api.hanzo.ai publishes. The
API took singular families and the snapshot kept the plurals, so the site's
catalog gate read each as a 404:

  agents /v1/agent, prompts /v1/prompt, evals /v1/eval,
  datasets /v1/eval/datasets, experiments /v1/experiment,
  scores /v1/eval/scores, score-configs /v1/eval/rubrics,
  functions /v1/function, service-mesh /v1/network/services,
  nodes /v1/validator, zero-trust /v1/network, wallet /v1/wallet,
  bot /v1/bot, referrals /v1/referral.

Two moved further than a spelling. Tasks is the durable engine
(hanzoai/tasks), which answers at /v1/workflow; /v1/task is the work board,
a different product. Integrations is the connector catalog, which the
integration app took as /v1/provider when it was renamed.

Every address above is a path in openapi.yaml. Correct carries them to the
store on the next boot. The Correct test now ages its row to the retired
plural, which is the drift production holds.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-25 01:00:36 -07:00

324 lines
12 KiB
Go

package catalogentry
import (
"strings"
"testing"
"github.com/hanzoai/commerce/util/test/ae"
)
// clientProducts is the exact set of catalog rows that CONSUME the API rather
// than serve one — the CLI, the SDKs, the API reference, the desktop app, the two
// web surfaces, and Edge.
//
// It is the SECOND WITNESS to the seed, and that is its whole job. All of them
// once carried an apiPath, and since none of them serves a route, every one of
// those paths was a dead link the catalog advertised as "enabled". Marking a row
// a client now costs a deliberate edit here too, next to the reason.
//
// Edge belongs here on the fleet's own argument, not a guess: manifest/apps.go
// gives "/v1/edge" to nobody because hanzoai/edge is the on-device inference
// runtime, a binary a customer runs on their own machine. It calls this API; it
// is not reached through it, and /v1/edge 404s at every depth on purpose.
//
// The IDE does NOT belong here, though it was once written down as if it did. The
// code-intelligence engine behind it is served — search, symbols and LSP at
// /v1/code — so calling it a client hid a working API instead of revealing a
// missing one.
var clientProducts = map[string]bool{
"api": true,
"cli": true,
"sdks": true,
"desktop": true,
"console": true,
"studio": true,
"edge": true,
}
// pendingProducts is the exact set of catalog rows that would serve an API and do
// not yet — real products with a console surface that api.hanzo.ai does not front.
// Each was searched for before it was written down here, and the search is the
// point: "missing" usually means MOVED, and eleven of these thirteen sibling rows
// turned out to be exactly that.
//
// - containers — no /v1/container* anywhere. The container-shaped surfaces the
// fleet does serve are already other products (Functions, Applications) or a
// differently-named one (/v1/sandboxes), so pointing here at any of them
// would dress up a product that is not shipped.
// - cdn — no prefix. Purging is a VERB on other products
// (/v1/projects/:slug/purge, /v1/cloudflare/zones/:zone/purge), never a noun.
// - hsm — no prefix. /v1/kms is the key service and it is MPC-rooted,
// not hardware-rooted; lending it to HSM would sell one as the other.
// - mpc — apps/mpc is a CLIENT LIBRARY for the separate luxfi/mpc ring.
// Nothing in this fleet serves it. MPC custody is REACHED through /v1/wallet,
// which is the Wallets product, not this one.
// - attestations — no prefix, no handler, nothing HTTP-addressable.
//
// The three DAO rows are pending and not client, which is the same distinction
// edge turns on read the other way: a client CONSUMES this API and serves none,
// and each of these serves one that is not ours. luxdao/api is the offchain API
// behind Governance and DaoWork — proposals, comments, temperature checks, SIWE —
// so luxdao/app calls THAT, never api.hanzo.ai. Calling them clients would claim
// a dependency on this API that does not exist.
//
// - safe — the DAO treasury multisig (luxdao/contracts). The fleet DOES serve
// two paths spelled safe and NEITHER is this product: /v1/captable/safes and
// /v1/company/fundraise/safe are the fundraising instrument, a simple
// agreement for future equity, which those operations say in their own prose.
// One noun, two meanings — pointing here at either would sell a cap-table
// note as a multisig, which is the dressing-up the address gate refuses.
// - governance — no /v1 segment resembles it, and none of the 193 the fleet
// publishes is a vote, proposal or DAO surface. Served at lux.vote.
// - daowork — the bounty surface of that same app, likewise unfronted here.
var pendingProducts = map[string]bool{
// vpc and load-balancer were served by cloud's apps/do, the DigitalOcean
// plane, and that app is gone — so /v1/vpcs and /v1/balancers are addresses
// the fleet no longer answers. The products are real; the API is not here.
"vpc": true,
"load-balancer": true,
"containers": true,
"cdn": true,
"hsm": true,
"mpc": true,
"attestations": true,
"safe": true,
"governance": true,
"daowork": true,
}
func TestSeedGivesEveryClientNoApiPath(t *testing.T) {
rows, err := HanzoSeedRows()
if err != nil {
t.Fatalf("HanzoSeedRows: %v", err)
}
seen := map[string]bool{}
for _, r := range rows {
switch r.Kind {
case KindClient, KindPending:
seen[r.Slug] = true
table, other := clientProducts, pendingProducts
if r.Kind == KindPending {
table, other = pendingProducts, clientProducts
}
if !table[r.Slug] {
t.Errorf("%s is seeded %s but is not in the %s table — one side moved alone", r.Slug, r.Kind, r.Kind)
}
if other[r.Slug] {
t.Errorf("%s is seeded %s while the other table also claims it — a row has one kind", r.Slug, r.Kind)
}
if r.ApiPath != "" || r.ApiRoute != "" {
t.Errorf("%s is %s yet advertises apiPath %q / apiRoute %q — it has no route of its own, so any path on it is a dead link",
r.Slug, r.Kind, r.ApiPath, r.ApiRoute)
}
case KindService, "":
if clientProducts[r.Slug] || pendingProducts[r.Slug] {
t.Errorf("%s serves no API but is seeded a service — it will be judged by an apiPath it does not have", r.Slug)
}
if !strings.HasPrefix(r.ApiPath, "/v1/") {
t.Errorf("%s: apiPath %q is not /v1-prefixed — a service is reached at a real route", r.Slug, r.ApiPath)
}
default:
t.Errorf("%s: kind %q is none of %q, %q, %q", r.Slug, r.Kind, KindService, KindClient, KindPending)
}
}
for _, table := range []map[string]bool{clientProducts, pendingProducts} {
for slug := range table {
if !seen[slug] {
t.Errorf("%s serves no API but no seed row says so — it stays counted against an apiPath", slug)
}
}
}
}
// An apiPath and its host-qualified twin are two spellings of one route. Left to
// drift they disagree, and a reader has no way to tell which one lies.
func TestSeedApiRouteAgreesWithApiPath(t *testing.T) {
rows, err := HanzoSeedRows()
if err != nil {
t.Fatalf("HanzoSeedRows: %v", err)
}
for _, r := range rows {
want := ""
if r.ApiPath != "" {
want = "api.hanzo.ai" + r.ApiPath
}
if r.ApiRoute != want {
t.Errorf("%s: apiRoute %q contradicts apiPath %q (want %q)", r.Slug, r.ApiRoute, r.ApiPath, want)
}
}
}
// billedRetail is what the enso service CHARGES, per MTok, as its billing
// catalog states it.
//
// This table is the SECOND WITNESS to the seed, and that is its whole job. The
// bug it exists to stop is not a typo — it is someone changing the published
// price on its own, which is exactly how the estate came to advertise 20/60
// while charging 4/20, and to advertise 3/12 BELOW what it charged. Editing the
// seed now costs a deliberate edit here too, next to the reason.
//
// If enso reprices, BOTH sides move together, in one commit, and the numbers
// come from the billing catalog — never from marketing copy, never from a docs
// page, never from the pre-reprice figures (20/60, 2/6, 40/120) which are stale
// and must not be restored.
var billedRetail = map[string]struct{ in, out string }{
"enso": {"4.00", "20.00"},
"enso-flash": {"2.00", "4.00"},
"enso-ultra": {"5.00", "25.00"},
"enso-vl": {"2.28", "9.60"},
"enso-vl-pro": {"9.00", "45.00"},
}
func TestEnsoSeedPublishesTheBilledPrice(t *testing.T) {
rows, err := EnsoSeedRows()
if err != nil {
t.Fatalf("EnsoSeedRows: %v", err)
}
if len(rows) != len(billedRetail) {
t.Fatalf("seed has %d SKUs, billed table has %d — a SKU was added or dropped on one side only",
len(rows), len(billedRetail))
}
for _, r := range rows {
want, ok := billedRetail[r.Slug]
if !ok {
t.Errorf("%s: seeded but not in the billed table — every seeded SKU must state what it charges", r.Slug)
continue
}
got := map[string]string{}
for _, rate := range r.Rates {
if rate.Unit != UnitMTok {
t.Errorf("%s: rate %q has unit %q, want %q — a token price is per MTok", r.Slug, rate.Key, rate.Unit, UnitMTok)
}
// MaxContext 0 means "all contexts". Enso has no rung ladder, and
// TokenCostCents only reads the 0 rung, so a non-zero here would make
// the price invisible to every reader.
if rate.MaxContext != 0 {
t.Errorf("%s: rate %q has MaxContext %d, want 0 — enso bills one rung per SKU",
r.Slug, rate.Key, rate.MaxContext)
}
got[rate.Key] = rate.Price
}
if got[RateIn] != want.in {
t.Errorf("%s input: seed publishes %q, enso bills %q — published price must EQUAL billed price",
r.Slug, got[RateIn], want.in)
}
if got[RateOut] != want.out {
t.Errorf("%s output: seed publishes %q, enso bills %q — published price must EQUAL billed price",
r.Slug, got[RateOut], want.out)
}
}
}
// A listed model with no price is a billing hole: it is selectable and there is
// no number to charge. Published rows must carry both components, priced.
func TestEnsoSeedNeverPublishesAnUnpricedModel(t *testing.T) {
rows, err := EnsoSeedRows()
if err != nil {
t.Fatalf("EnsoSeedRows: %v", err)
}
for _, r := range rows {
if !r.Published {
continue
}
for _, key := range []string{RateIn, RateOut} {
found := false
for _, rate := range r.Rates {
if rate.Key == key && rate.Price != "" {
found = true
}
}
if !found {
t.Errorf("%s is published with no %s price — a selectable model with no rate is a billing hole", r.Slug, key)
}
}
}
}
// The vision engines are reached only through the family's vision_fallback and
// are marked "NEVER listed SKUs" in the billing catalog. Seeding them published
// would put two internal engines on the public price list.
func TestEnsoSeedKeepsTheVisionEnginesUnlisted(t *testing.T) {
rows, err := EnsoSeedRows()
if err != nil {
t.Fatalf("EnsoSeedRows: %v", err)
}
internal := map[string]bool{"enso-vl": true, "enso-vl-pro": true}
for _, r := range rows {
if internal[r.Slug] && r.Published {
t.Errorf("%s is seeded published — it is an internal vision engine, never a listed SKU", r.Slug)
}
if !internal[r.Slug] && !r.Published {
t.Errorf("%s is seeded unpublished — the three public SKUs must be listed for discovery", r.Slug)
}
}
}
// TestEnsoSeedIsNotBlockedByAForeignRow is the production defect, pinned.
//
// A deployment carried one enso-category row this seed does not contain
// (enso-free, created by another path). The old guard counted CATEGORY rows, saw
// one, and skipped — so enso, enso-flash and enso-ultra were never created and
// every paid tier answered as an unknown model while the catalog served a lone
// free tier. Nothing went red: the guard was doing exactly what it said.
func TestEnsoSeedIsNotBlockedByAForeignRow(t *testing.T) {
c := ae.NewContext()
defer c.Close()
db := sysDB(c)
// A row in the enso category that the seed does not contain.
foreign := New(db)
foreign.Slug = "enso-free"
foreign.Name = "Enso Free"
foreign.Category = CategoryEnso
if err := foreign.Put(); err != nil {
t.Fatalf("seeding the foreign row: %v", err)
}
created, err := SeedEnsoModelsIfEmpty(db)
if err != nil {
t.Fatalf("SeedEnsoModelsIfEmpty: %v", err)
}
if created == 0 {
t.Fatal("a foreign enso row blocked the seed — the paid tiers would never exist")
}
for _, slug := range []string{"enso", "enso-flash", "enso-ultra"} {
e := New(db)
ok, qerr := e.Query().Filter("Slug=", slug).Get()
if qerr != nil {
t.Fatalf("query %s: %v", slug, qerr)
}
if !ok {
t.Errorf("%s was not seeded", slug)
}
}
}
// TestEnsoSeedRespectsCuration is the half the guard exists for: once the seed
// HAS run, a deletion is authoritative and re-running must not resurrect it.
func TestEnsoSeedRespectsCuration(t *testing.T) {
c := ae.NewContext()
defer c.Close()
db := sysDB(c)
if _, err := SeedEnsoModelsIfEmpty(db); err != nil {
t.Fatalf("first seed: %v", err)
}
// An admin retires one SKU.
e := New(db)
ok, err := e.Query().Filter("Slug=", "enso-ultra").Get()
if err != nil || !ok {
t.Fatalf("enso-ultra missing after the seed: ok=%v err=%v", ok, err)
}
if err := e.Delete(); err != nil {
t.Fatalf("delete: %v", err)
}
created, err := SeedEnsoModelsIfEmpty(db)
if err != nil {
t.Fatalf("second seed: %v", err)
}
if created != 0 {
t.Errorf("re-seeded %d rows after the seed had run — a deletion must stay deleted", created)
}
}