Documentation
¶
Overview ¶
Package plan is the plan catalog: every tier you can buy, what it costs, and what it grants.
It serves /v1/plans/* — cloud, subscription, blockchain, DNS, GPU and storage tiers, the entitlement vocabulary each grants and its JSON Schema, and a resolver from a plan id to both. It is the catalog of RECORD; apps/pricing reads the same @hanzo/plans source and answers eight of these sections again under /v1/pricing/*.
STRATEGY: wrap, don't rewrite. @hanzo/plans is a Node data package (JSON catalog + entitlements.mjs transforms). We do NOT reimplement the entitlement vocabulary in Go and we do NOT copy the catalog into cloud. Instead:
- github.com/hanzoai/plans (the service repo's Go embed module) ships goja/bundle.js — the ESM-free port of entitlements.mjs + the /v1/plans route table — plus the embedded *.json catalog (plans.Data()).
- This wrapper loads that bundle into a goja runtime (apps/goja), injects the catalog as globalThis.__PLANS_DATA__, and declares one TYPED op per address (ops.go) that calls globalThis.handle({route, params, tenant}). The entitlement transforms (fromLegacy/toLicenseFeatures/resolvePlan) run in goja — real JS, not a Go reimplementation.
The plans data is read-only public-catalog content; there are no secrets here. The licensing SIGNER/fingerprint that consumes toLicenseFeatures stays in hanzoai/licensing. This wrapper is pure glue.
IAM gating + X-Org-Id tenant scope: every /v1/plans route threads the VALIDATED org into the bundle as the tenant, so a reseller org (tenant_id != "hanzo") sees its own catalog overrides. A typed op receives only a context, so that org arrives on the context (cloud.Bridge parks it, ops.go/catalogTenant reads it) and is never an In field — an In field is caller-supplied, and a tenant read from one would hand any caller any reseller's catalog. The plan catalog is readable by any authenticated caller; no admin scope is required for reads.
Index ¶
- func Entitlements(ctx context.Context, id string) (map[string]any, error)
- func LicenseEntitlement(ctx context.Context, id string) (entitlements map[string]any, features []string, found bool, err error)
- func Mount(app cloud.Router, deps cloud.Deps) error
- func Paid(t Tier) bool
- func Shutdown(context.Context) error
- func Tokens(l Licence) []string
- type Licence
- type Tier
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Entitlements ¶
Entitlements resolves the canonical entitlement block for a plan id from the @hanzo/plans catalog (the single source of truth). It runs the bundle's "entitlements" route on the shared goja host and returns the parsed, namespaced `entitlements` map (e.g. "world.api_rate_limit", "ai.tokens_per_min"). This is the one Go seam other subsystems use to read plan entitlements without importing the catalog data or reimplementing the fromLegacy derivation. Tenant is the public "hanzo" catalog (plan entitlements are tenant-independent metadata).
It errors if the plans subsystem is not mounted or the plan id is unknown, so callers can fail closed rather than silently granting a default tier.
func LicenseEntitlement ¶
func LicenseEntitlement(ctx context.Context, id string) (entitlements map[string]any, features []string, found bool, err error)
LicenseEntitlement resolves BOTH the canonical entitlement block AND the flat license-feature list for a plan id from the @hanzo/plans catalog (the single source of truth). It runs the bundle's "entitlements" route on the shared goja host — the SAME route /v1/plans/entitlements/:id serves — and returns the parsed `entitlements` map plus the `license_features` list the bundle's toLicenseFeatures transform produces. It is the seam the commerce entitlement resolver (commerce.CheckEntitlement) uses to map a subscription's plan tier to the flat features a signed license carries, WITHOUT reimplementing the vocabulary in Go (the entitlement transforms stay in one place — the JS bundle).
found reports whether the plan id exists in the catalog. A 404 from the bundle is (nil, nil, false, nil): a real "unknown plan", NOT a machinery error — so a caller scanning several subscriptions can skip an unknown tier and keep going. ANY other failure (plans subsystem not mounted, dispatch error, non-200/404 status, decode error) returns a non-nil error so the money-path caller FAILS CLOSED rather than treating an unresolved plan as "grants nothing".
func Paid ¶
Paid reports whether t is a PAID Hanzo cloud account tier — the paywall's "this org holds a real plan" predicate. It is true when the tier is a cloud account category AND it costs money: a published price above zero, or a negotiated contact-sales contract.
A free cloud tier (priced zero), any tier from another product line whatever it costs, and a zero Tier are all false. It NEVER fabricates a grant.
func Tokens ¶ added in v1.801.477
Tokens returns l as the flat license-feature list: engine features verbatim, plus one namespaced token per licensed app and product. Sorted and deduplicated so the same licence always produces the same list, and so a token issued from it is byte-stable across processes.
A tier that licenses nothing yields an empty list — never a token, never a grant.
Types ¶
type Licence ¶ added in v1.801.477
type Licence struct {
// Products are the commerce SKUs the tier entitles ("engine", "team", …).
Products []string
// Apps are the engine app builds it licenses ("hanzo", "lux", "zoo").
Apps []string
// Features are engine capability tokens granted verbatim ("inference", …).
Features []string
}
Licence is the licensing facts of one tier — the subset of a commerce plan row this package needs to state the rule without importing the commerce models, for the same reason Tier exists.
type Tier ¶ added in v1.801.477
type Tier struct {
// Category is the plan family — "personal"/"team"/"enterprise" are cloud account
// tiers; "world"/"social"/"dns" are their own products.
Category string
// Price is the recurring charge in cents. Zero is the free tier.
Price int64
// ContactSales marks a negotiated tier whose price is not published (Custom,
// Enterprise). It costs money; the amount just is not in the catalog.
ContactSales bool
}
Tier is the money facts of one plan: what product line it belongs to and whether it costs anything. It is the subset of a commerce plan row this predicate needs, so apps/plan states the paywall rule without importing the commerce models.