plan

package
v1.801.477 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func Entitlements

func Entitlements(ctx context.Context, id string) (map[string]any, error)

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 Mount

func Mount(app cloud.Router, deps cloud.Deps) error

Mount registers the /v1/plans/* surface on app per HIP-0106.

func Paid(t Tier) bool

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 Shutdown

func Shutdown(context.Context) error

Shutdown drops the goja host. Idempotent.

func Tokens added in v1.801.477

func Tokens(l Licence) []string

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.

Jump to

Keyboard shortcuts

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